about summary refs log tree commit diff
path: root/nixos/modules/services/web-apps
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/web-apps')
-rw-r--r--nixos/modules/services/web-apps/akkoma.md352
-rw-r--r--nixos/modules/services/web-apps/akkoma.nix1141
-rw-r--r--nixos/modules/services/web-apps/alps.nix133
-rw-r--r--nixos/modules/services/web-apps/anuko-time-tracker.nix388
-rw-r--r--nixos/modules/services/web-apps/artalk.nix131
-rw-r--r--nixos/modules/services/web-apps/atlassian/confluence.nix224
-rw-r--r--nixos/modules/services/web-apps/atlassian/crowd.nix193
-rw-r--r--nixos/modules/services/web-apps/atlassian/jira.nix219
-rw-r--r--nixos/modules/services/web-apps/audiobookshelf.nix90
-rw-r--r--nixos/modules/services/web-apps/bookstack.nix451
-rw-r--r--nixos/modules/services/web-apps/c2fmzq-server.md42
-rw-r--r--nixos/modules/services/web-apps/c2fmzq-server.nix130
-rw-r--r--nixos/modules/services/web-apps/calibre-web.nix170
-rw-r--r--nixos/modules/services/web-apps/castopod.md25
-rw-r--r--nixos/modules/services/web-apps/castopod.nix318
-rw-r--r--nixos/modules/services/web-apps/changedetection-io.nix220
-rw-r--r--nixos/modules/services/web-apps/chatgpt-retrieval-plugin.nix106
-rw-r--r--nixos/modules/services/web-apps/cloudlog.nix503
-rw-r--r--nixos/modules/services/web-apps/code-server.nix260
-rw-r--r--nixos/modules/services/web-apps/coder.nix227
-rw-r--r--nixos/modules/services/web-apps/commafeed.nix114
-rw-r--r--nixos/modules/services/web-apps/convos.nix72
-rw-r--r--nixos/modules/services/web-apps/crabfit.nix171
-rw-r--r--nixos/modules/services/web-apps/davis.md32
-rw-r--r--nixos/modules/services/web-apps/davis.nix554
-rw-r--r--nixos/modules/services/web-apps/dex.nix132
-rw-r--r--nixos/modules/services/web-apps/discourse.md296
-rw-r--r--nixos/modules/services/web-apps/discourse.nix1093
-rw-r--r--nixos/modules/services/web-apps/documize.nix130
-rw-r--r--nixos/modules/services/web-apps/dokuwiki.nix519
-rw-r--r--nixos/modules/services/web-apps/dolibarr.nix325
-rw-r--r--nixos/modules/services/web-apps/engelsystem.nix181
-rw-r--r--nixos/modules/services/web-apps/ethercalc.nix57
-rw-r--r--nixos/modules/services/web-apps/filesender.md49
-rw-r--r--nixos/modules/services/web-apps/filesender.nix253
-rw-r--r--nixos/modules/services/web-apps/firefly-iii.nix381
-rw-r--r--nixos/modules/services/web-apps/flarum.nix210
-rw-r--r--nixos/modules/services/web-apps/fluidd.nix61
-rw-r--r--nixos/modules/services/web-apps/freshrss.nix304
-rw-r--r--nixos/modules/services/web-apps/galene.nix207
-rw-r--r--nixos/modules/services/web-apps/gerrit.nix232
-rw-r--r--nixos/modules/services/web-apps/gotify-server.nix49
-rw-r--r--nixos/modules/services/web-apps/gotosocial.md68
-rw-r--r--nixos/modules/services/web-apps/gotosocial.nix171
-rw-r--r--nixos/modules/services/web-apps/grocy.md66
-rw-r--r--nixos/modules/services/web-apps/grocy.nix184
-rw-r--r--nixos/modules/services/web-apps/guacamole-client.nix60
-rw-r--r--nixos/modules/services/web-apps/guacamole-server.nix83
-rw-r--r--nixos/modules/services/web-apps/healthchecks.nix271
-rw-r--r--nixos/modules/services/web-apps/hedgedoc.nix321
-rw-r--r--nixos/modules/services/web-apps/hledger-web.nix131
-rw-r--r--nixos/modules/services/web-apps/honk.md23
-rw-r--r--nixos/modules/services/web-apps/honk.nix153
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix262
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix157
-rw-r--r--nixos/modules/services/web-apps/invidious.nix403
-rw-r--r--nixos/modules/services/web-apps/invoiceplane.nix400
-rw-r--r--nixos/modules/services/web-apps/isso.nix91
-rw-r--r--nixos/modules/services/web-apps/jirafeau.nix166
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.md45
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix638
-rw-r--r--nixos/modules/services/web-apps/kasmweb/default.nix275
-rw-r--r--nixos/modules/services/web-apps/kasmweb/initialize_kasmweb.sh114
-rw-r--r--nixos/modules/services/web-apps/kavita.nix106
-rw-r--r--nixos/modules/services/web-apps/keycloak.md143
-rw-r--r--nixos/modules/services/web-apps/keycloak.nix684
-rw-r--r--nixos/modules/services/web-apps/komga.nix122
-rw-r--r--nixos/modules/services/web-apps/lanraragi.nix93
-rw-r--r--nixos/modules/services/web-apps/lemmy.md33
-rw-r--r--nixos/modules/services/web-apps/lemmy.nix315
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix309
-rw-r--r--nixos/modules/services/web-apps/mainsail.nix61
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix917
-rw-r--r--nixos/modules/services/web-apps/matomo.md77
-rw-r--r--nixos/modules/services/web-apps/matomo.nix322
-rw-r--r--nixos/modules/services/web-apps/mattermost.nix348
-rw-r--r--nixos/modules/services/web-apps/mealie.nix78
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix642
-rw-r--r--nixos/modules/services/web-apps/meme-bingo-web.nix88
-rw-r--r--nixos/modules/services/web-apps/microbin.nix93
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix154
-rw-r--r--nixos/modules/services/web-apps/mobilizon.nix448
-rw-r--r--nixos/modules/services/web-apps/monica.nix468
-rw-r--r--nixos/modules/services/web-apps/moodle.nix314
-rw-r--r--nixos/modules/services/web-apps/movim.nix709
-rw-r--r--nixos/modules/services/web-apps/netbox.nix395
-rw-r--r--nixos/modules/services/web-apps/nextcloud-notify_push.nix124
-rw-r--r--nixos/modules/services/web-apps/nextcloud.md242
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix1244
-rw-r--r--nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix87
-rw-r--r--nixos/modules/services/web-apps/nexus.nix152
-rw-r--r--nixos/modules/services/web-apps/nifi.nix321
-rw-r--r--nixos/modules/services/web-apps/node-red.nix135
-rw-r--r--nixos/modules/services/web-apps/ocis.md113
-rw-r--r--nixos/modules/services/web-apps/ocis.nix201
-rw-r--r--nixos/modules/services/web-apps/onlyoffice.nix291
-rw-r--r--nixos/modules/services/web-apps/openvscode-server.nix213
-rw-r--r--nixos/modules/services/web-apps/openwebrx.nix33
-rw-r--r--nixos/modules/services/web-apps/outline.nix791
-rw-r--r--nixos/modules/services/web-apps/peering-manager.nix344
-rw-r--r--nixos/modules/services/web-apps/peertube.nix857
-rw-r--r--nixos/modules/services/web-apps/pgpkeyserver-lite.nix71
-rw-r--r--nixos/modules/services/web-apps/photoprism.nix156
-rw-r--r--nixos/modules/services/web-apps/phylactery.nix46
-rw-r--r--nixos/modules/services/web-apps/pict-rs.md91
-rw-r--r--nixos/modules/services/web-apps/pict-rs.nix100
-rw-r--r--nixos/modules/services/web-apps/pixelfed.nix479
-rw-r--r--nixos/modules/services/web-apps/plantuml-server.nix153
-rw-r--r--nixos/modules/services/web-apps/plausible.md35
-rw-r--r--nixos/modules/services/web-apps/plausible.nix334
-rw-r--r--nixos/modules/services/web-apps/powerdns-admin.nix153
-rw-r--r--nixos/modules/services/web-apps/pretalx.nix471
-rw-r--r--nixos/modules/services/web-apps/pretix.nix581
-rw-r--r--nixos/modules/services/web-apps/prosody-filer.nix86
-rw-r--r--nixos/modules/services/web-apps/rimgo.nix107
-rw-r--r--nixos/modules/services/web-apps/rss-bridge.nix150
-rw-r--r--nixos/modules/services/web-apps/selfoss.nix164
-rw-r--r--nixos/modules/services/web-apps/sftpgo.nix368
-rw-r--r--nixos/modules/services/web-apps/shiori.nix98
-rw-r--r--nixos/modules/services/web-apps/silverbullet.nix123
-rw-r--r--nixos/modules/services/web-apps/simplesamlphp.nix128
-rw-r--r--nixos/modules/services/web-apps/slskd.nix333
-rw-r--r--nixos/modules/services/web-apps/snipe-it.nix515
-rw-r--r--nixos/modules/services/web-apps/sogo.nix271
-rw-r--r--nixos/modules/services/web-apps/suwayomi-server.md111
-rw-r--r--nixos/modules/services/web-apps/suwayomi-server.nix226
-rw-r--r--nixos/modules/services/web-apps/trilium.nix155
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix669
-rw-r--r--nixos/modules/services/web-apps/vikunja.nix118
-rw-r--r--nixos/modules/services/web-apps/whitebophir.nix47
-rw-r--r--nixos/modules/services/web-apps/wiki-js.nix142
-rw-r--r--nixos/modules/services/web-apps/windmill.nix177
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix568
-rw-r--r--nixos/modules/services/web-apps/writefreely.nix482
-rw-r--r--nixos/modules/services/web-apps/your_spotify.nix191
-rw-r--r--nixos/modules/services/web-apps/youtrack.md30
-rw-r--r--nixos/modules/services/web-apps/youtrack.nix269
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix233
-rw-r--r--nixos/modules/services/web-apps/zitadel.nix223
139 files changed, 0 insertions, 35548 deletions
diff --git a/nixos/modules/services/web-apps/akkoma.md b/nixos/modules/services/web-apps/akkoma.md
deleted file mode 100644
index 13b074b228a4..000000000000
--- a/nixos/modules/services/web-apps/akkoma.md
+++ /dev/null
@@ -1,352 +0,0 @@
-# Akkoma {#module-services-akkoma}
-
-[Akkoma](https://akkoma.dev/) is a lightweight ActivityPub microblogging server forked from Pleroma.
-
-## Service configuration {#modules-services-akkoma-service-configuration}
-
-The Elixir configuration file required by Akkoma is generated automatically from
-[{option}`services.akkoma.config`](options.html#opt-services.akkoma.config). Secrets must be
-included from external files outside of the Nix store by setting the configuration option to
-an attribute set containing the attribute {option}`_secret` – a string pointing to the file
-containing the actual value of the option.
-
-For the mandatory configuration settings these secrets will be generated automatically if the
-referenced file does not exist during startup, unless disabled through
-[{option}`services.akkoma.initSecrets`](options.html#opt-services.akkoma.initSecrets).
-
-The following configuration binds Akkoma to the Unix socket `/run/akkoma/socket`, expecting to
-be run behind a HTTP proxy on `fediverse.example.com`.
-
-
-```nix
-{
-  services.akkoma.enable = true;
-  services.akkoma.config = {
-    ":pleroma" = {
-      ":instance" = {
-        name = "My Akkoma instance";
-        description = "More detailed description";
-        email = "admin@example.com";
-        registration_open = false;
-      };
-
-      "Pleroma.Web.Endpoint" = {
-        url.host = "fediverse.example.com";
-      };
-    };
-  };
-}
-```
-
-Please refer to the [configuration cheat sheet](https://docs.akkoma.dev/stable/configuration/cheatsheet/)
-for additional configuration options.
-
-## User management {#modules-services-akkoma-user-management}
-
-After the Akkoma service is running, the administration utility can be used to
-[manage users](https://docs.akkoma.dev/stable/administration/CLI_tasks/user/). In particular an
-administrative user can be created with
-
-```ShellSession
-$ pleroma_ctl user new <nickname> <email> --admin --moderator --password <password>
-```
-
-## Proxy configuration {#modules-services-akkoma-proxy-configuration}
-
-Although it is possible to expose Akkoma directly, it is common practice to operate it behind an
-HTTP reverse proxy such as nginx.
-
-```nix
-{
-  services.akkoma.nginx = {
-    enableACME = true;
-    forceSSL = true;
-  };
-
-  services.nginx = {
-    enable = true;
-
-    clientMaxBodySize = "16m";
-    recommendedTlsSettings = true;
-    recommendedOptimisation = true;
-    recommendedGzipSettings = true;
-  };
-}
-```
-
-Please refer to [](#module-security-acme) for details on how to provision an SSL/TLS certificate.
-
-### Media proxy {#modules-services-akkoma-media-proxy}
-
-Without the media proxy function, Akkoma does not store any remote media like pictures or video
-locally, and clients have to fetch them directly from the source server.
-
-```nix
-{
-  # Enable nginx slice module distributed with Tengine
-  services.nginx.package = pkgs.tengine;
-
-  # Enable media proxy
-  services.akkoma.config.":pleroma".":media_proxy" = {
-    enabled = true;
-    proxy_opts.redirect_on_failure = true;
-  };
-
-  # Adjust the persistent cache size as needed:
-  #  Assuming an average object size of 128 KiB, around 1 MiB
-  #  of memory is required for the key zone per GiB of cache.
-  # Ensure that the cache directory exists and is writable by nginx.
-  services.nginx.commonHttpConfig = ''
-    proxy_cache_path /var/cache/nginx/cache/akkoma-media-cache
-      levels= keys_zone=akkoma_media_cache:16m max_size=16g
-      inactive=1y use_temp_path=off;
-  '';
-
-  services.akkoma.nginx = {
-    locations."/proxy" = {
-      proxyPass = "http://unix:/run/akkoma/socket";
-
-      extraConfig = ''
-        proxy_cache akkoma_media_cache;
-
-        # Cache objects in slices of 1 MiB
-        slice 1m;
-        proxy_cache_key $host$uri$is_args$args$slice_range;
-        proxy_set_header Range $slice_range;
-
-        # Decouple proxy and upstream responses
-        proxy_buffering on;
-        proxy_cache_lock on;
-        proxy_ignore_client_abort on;
-
-        # Default cache times for various responses
-        proxy_cache_valid 200 1y;
-        proxy_cache_valid 206 301 304 1h;
-
-        # Allow serving of stale items
-        proxy_cache_use_stale error timeout invalid_header updating;
-      '';
-    };
-  };
-}
-```
-
-#### Prefetch remote media {#modules-services-akkoma-prefetch-remote-media}
-
-The following example enables the `MediaProxyWarmingPolicy` MRF policy which automatically
-fetches all media associated with a post through the media proxy, as soon as the post is
-received by the instance.
-
-```nix
-{
-  services.akkoma.config.":pleroma".":mrf".policies =
-    map (pkgs.formats.elixirConf { }).lib.mkRaw [
-      "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy"
-  ];
-}
-```
-
-#### Media previews {#modules-services-akkoma-media-previews}
-
-Akkoma can generate previews for media.
-
-```nix
-{
-  services.akkoma.config.":pleroma".":media_preview_proxy" = {
-    enabled = true;
-    thumbnail_max_width = 1920;
-    thumbnail_max_height = 1080;
-  };
-}
-```
-
-## Frontend management {#modules-services-akkoma-frontend-management}
-
-Akkoma will be deployed with the `akkoma-fe` and `admin-fe` frontends by default. These can be
-modified by setting
-[{option}`services.akkoma.frontends`](options.html#opt-services.akkoma.frontends).
-
-The following example overrides the primary frontend’s default configuration using a custom
-derivation.
-
-```nix
-{
-  services.akkoma.frontends.primary.package = pkgs.runCommand "akkoma-fe" {
-    config = builtins.toJSON {
-      expertLevel = 1;
-      collapseMessageWithSubject = false;
-      stopGifs = false;
-      replyVisibility = "following";
-      webPushHideIfCW = true;
-      hideScopeNotice = true;
-      renderMisskeyMarkdown = false;
-      hideSiteFavicon = true;
-      postContentType = "text/markdown";
-      showNavShortcuts = false;
-    };
-    nativeBuildInputs = with pkgs; [ jq xorg.lndir ];
-    passAsFile = [ "config" ];
-  } ''
-    mkdir $out
-    lndir ${pkgs.akkoma-frontends.akkoma-fe} $out
-
-    rm $out/static/config.json
-    jq -s add ${pkgs.akkoma-frontends.akkoma-fe}/static/config.json ${config} \
-      >$out/static/config.json
-  '';
-}
-```
-
-## Federation policies {#modules-services-akkoma-federation-policies}
-
-Akkoma comes with a number of modules to police federation with other ActivityPub instances.
-The most valuable for typical users is the
-[`:mrf_simple`](https://docs.akkoma.dev/stable/configuration/cheatsheet/#mrf_simple) module
-which allows limiting federation based on instance hostnames.
-
-This configuration snippet provides an example on how these can be used. Choosing an adequate
-federation policy is not trivial and entails finding a balance between connectivity to the rest
-of the fediverse and providing a pleasant experience to the users of an instance.
-
-
-```nix
-{
-  services.akkoma.config.":pleroma" = with (pkgs.formats.elixirConf { }).lib; {
-    ":mrf".policies = map mkRaw [
-      "Pleroma.Web.ActivityPub.MRF.SimplePolicy"
-    ];
-
-    ":mrf_simple" = {
-      # Tag all media as sensitive
-      media_nsfw = mkMap {
-        "nsfw.weird.kinky" = "Untagged NSFW content";
-      };
-
-      # Reject all activities except deletes
-      reject = mkMap {
-        "kiwifarms.cc" = "Persistent harassment of users, no moderation";
-      };
-
-      # Force posts to be visible by followers only
-      followers_only = mkMap {
-        "beta.birdsite.live" = "Avoid polluting timelines with Twitter posts";
-      };
-    };
-  };
-}
-```
-
-## Upload filters {#modules-services-akkoma-upload-filters}
-
-This example strips GPS and location metadata from uploads, deduplicates them and anonymises the
-the file name.
-
-```nix
-{
-  services.akkoma.config.":pleroma"."Pleroma.Upload".filters =
-    map (pkgs.formats.elixirConf { }).lib.mkRaw [
-      "Pleroma.Upload.Filter.Exiftool"
-      "Pleroma.Upload.Filter.Dedupe"
-      "Pleroma.Upload.Filter.AnonymizeFilename"
-    ];
-}
-```
-
-## Migration from Pleroma {#modules-services-akkoma-migration-pleroma}
-
-Pleroma instances can be migrated to Akkoma either by copying the database and upload data or by
-pointing Akkoma to the existing data. The necessary database migrations are run automatically
-during startup of the service.
-
-The configuration has to be copy‐edited manually.
-
-Depending on the size of the database, the initial migration may take a long time and exceed the
-startup timeout of the system manager. To work around this issue one may adjust the startup timeout
-{option}`systemd.services.akkoma.serviceConfig.TimeoutStartSec` or simply run the migrations
-manually:
-
-```ShellSession
-pleroma_ctl migrate
-```
-
-### Copying data {#modules-services-akkoma-migration-pleroma-copy}
-
-Copying the Pleroma data instead of re‐using it in place may permit easier reversion to Pleroma,
-but allows the two data sets to diverge.
-
-First disable Pleroma and then copy its database and upload data:
-
-```ShellSession
-# Create a copy of the database
-nix-shell -p postgresql --run 'createdb -T pleroma akkoma'
-
-# Copy upload data
-mkdir /var/lib/akkoma
-cp -R --reflink=auto /var/lib/pleroma/uploads /var/lib/akkoma/
-```
-
-After the data has been copied, enable the Akkoma service and verify that the migration has been
-successful. If no longer required, the original data may then be deleted:
-
-```ShellSession
-# Delete original database
-nix-shell -p postgresql --run 'dropdb pleroma'
-
-# Delete original Pleroma state
-rm -r /var/lib/pleroma
-```
-
-### Re‐using data {#modules-services-akkoma-migration-pleroma-reuse}
-
-To re‐use the Pleroma data in place, disable Pleroma and enable Akkoma, pointing it to the
-Pleroma database and upload directory.
-
-```nix
-{
-  # Adjust these settings according to the database name and upload directory path used by Pleroma
-  services.akkoma.config.":pleroma"."Pleroma.Repo".database = "pleroma";
-  services.akkoma.config.":pleroma".":instance".upload_dir = "/var/lib/pleroma/uploads";
-}
-```
-
-Please keep in mind that after the Akkoma service has been started, any migrations applied by
-Akkoma have to be rolled back before the database can be used again with Pleroma. This can be
-achieved through `pleroma_ctl ecto.rollback`. Refer to the
-[Ecto SQL documentation](https://hexdocs.pm/ecto_sql/Mix.Tasks.Ecto.Rollback.html) for
-details.
-
-## Advanced deployment options {#modules-services-akkoma-advanced-deployment}
-
-### Confinement {#modules-services-akkoma-confinement}
-
-The Akkoma systemd service may be confined to a chroot with
-
-```nix
-{
-  services.systemd.akkoma.confinement.enable = true;
-}
-```
-
-Confinement of services is not generally supported in NixOS and therefore disabled by default.
-Depending on the Akkoma configuration, the default confinement settings may be insufficient and
-lead to subtle errors at run time, requiring adjustment:
-
-Use
-[{option}`services.systemd.akkoma.confinement.packages`](options.html#opt-systemd.services._name_.confinement.packages)
-to make packages available in the chroot.
-
-{option}`services.systemd.akkoma.serviceConfig.BindPaths` and
-{option}`services.systemd.akkoma.serviceConfig.BindReadOnlyPaths` permit access to outside paths
-through bind mounts. Refer to
-[`BindPaths=`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#BindPaths=)
-of {manpage}`systemd.exec(5)` for details.
-
-### Distributed deployment {#modules-services-akkoma-distributed-deployment}
-
-Being an Elixir application, Akkoma can be deployed in a distributed fashion.
-
-This requires setting
-[{option}`services.akkoma.dist.address`](options.html#opt-services.akkoma.dist.address) and
-[{option}`services.akkoma.dist.cookie`](options.html#opt-services.akkoma.dist.cookie). The
-specifics depend strongly on the deployment environment. For more information please check the
-relevant [Erlang documentation](https://www.erlang.org/doc/reference_manual/distributed.html).
diff --git a/nixos/modules/services/web-apps/akkoma.nix b/nixos/modules/services/web-apps/akkoma.nix
deleted file mode 100644
index 8ba3c7eaa1e6..000000000000
--- a/nixos/modules/services/web-apps/akkoma.nix
+++ /dev/null
@@ -1,1141 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.akkoma;
-  ex = cfg.config;
-  db = ex.":pleroma"."Pleroma.Repo";
-  web = ex.":pleroma"."Pleroma.Web.Endpoint";
-
-  isConfined = config.systemd.services.akkoma.confinement.enable;
-  hasSmtp = (attrByPath [ ":pleroma" "Pleroma.Emails.Mailer" "adapter" "value" ] null ex) == "Swoosh.Adapters.SMTP";
-
-  isAbsolutePath = v: isString v && substring 0 1 v == "/";
-  isSecret = v: isAttrs v && v ? _secret && isAbsolutePath v._secret;
-
-  absolutePath = with types; mkOptionType {
-    name = "absolutePath";
-    description = "absolute path";
-    descriptionClass = "noun";
-    check = isAbsolutePath;
-    inherit (str) merge;
-  };
-
-  secret = mkOptionType {
-    name = "secret";
-    description = "secret value";
-    descriptionClass = "noun";
-    check = isSecret;
-    nestedTypes = {
-      _secret = absolutePath;
-    };
-  };
-
-  ipAddress = with types; mkOptionType {
-    name = "ipAddress";
-    description = "IPv4 or IPv6 address";
-    descriptionClass = "conjunction";
-    check = x: str.check x && builtins.match "[.0-9:A-Fa-f]+" x != null;
-    inherit (str) merge;
-  };
-
-  elixirValue = let
-    elixirValue' = with types;
-      nullOr (oneOf [ bool int float str (attrsOf elixirValue') (listOf elixirValue') ]) // {
-        description = "Elixir value";
-      };
-  in elixirValue';
-
-  frontend = {
-    options = {
-      package = mkOption {
-        type = types.package;
-        description = "Akkoma frontend package.";
-        example = literalExpression "pkgs.akkoma-frontends.akkoma-fe";
-      };
-
-      name = mkOption {
-        type = types.nonEmptyStr;
-        description = "Akkoma frontend name.";
-        example = "akkoma-fe";
-      };
-
-      ref = mkOption {
-        type = types.nonEmptyStr;
-        description = "Akkoma frontend reference.";
-        example = "stable";
-      };
-    };
-  };
-
-  sha256 = builtins.hashString "sha256";
-
-  replaceSec = let
-    replaceSec' = { }@args: v:
-      if isAttrs v
-        then if v ? _secret
-          then if isAbsolutePath v._secret
-            then sha256 v._secret
-            else abort "Invalid secret path (_secret = ${v._secret})"
-          else mapAttrs (_: val: replaceSec' args val) v
-        else if isList v
-          then map (replaceSec' args) v
-          else v;
-    in replaceSec' { };
-
-  # Erlang/Elixir uses a somewhat special format for IP addresses
-  erlAddr = addr: fileContents
-    (pkgs.runCommand addr {
-      nativeBuildInputs = [ cfg.package.elixirPackage ];
-      code = ''
-        case :inet.parse_address('${addr}') do
-          {:ok, addr} -> IO.inspect addr
-          {:error, _} -> System.halt(65)
-        end
-      '';
-      passAsFile = [ "code" ];
-    } ''elixir "$codePath" >"$out"'');
-
-  format = pkgs.formats.elixirConf { elixir = cfg.package.elixirPackage; };
-  configFile = format.generate "config.exs"
-    (replaceSec
-      (attrsets.updateManyAttrsByPath [{
-        path = [ ":pleroma" "Pleroma.Web.Endpoint" "http" "ip" ];
-        update = addr:
-          if isAbsolutePath addr
-            then format.lib.mkTuple
-              [ (format.lib.mkAtom ":local") addr ]
-            else format.lib.mkRaw (erlAddr addr);
-      }] cfg.config));
-
-  writeShell = { name, text, runtimeInputs ? [ ] }:
-    pkgs.writeShellApplication { inherit name text runtimeInputs; } + "/bin/${name}";
-
-  genScript = writeShell {
-    name = "akkoma-gen-cookie";
-    runtimeInputs = with pkgs; [ coreutils util-linux ];
-    text = ''
-      install -m 0400 \
-        -o ${escapeShellArg cfg.user } \
-        -g ${escapeShellArg cfg.group} \
-        <(hexdump -n 16 -e '"%02x"' /dev/urandom) \
-        "''${RUNTIME_DIRECTORY%%:*}/cookie"
-    '';
-  };
-
-  copyScript = writeShell {
-    name = "akkoma-copy-cookie";
-    runtimeInputs = with pkgs; [ coreutils ];
-    text = ''
-      install -m 0400 \
-        -o ${escapeShellArg cfg.user} \
-        -g ${escapeShellArg cfg.group} \
-        ${escapeShellArg cfg.dist.cookie._secret} \
-        "''${RUNTIME_DIRECTORY%%:*}/cookie"
-    '';
-  };
-
-  secretPaths = catAttrs "_secret" (collect isSecret cfg.config);
-
-  vapidKeygen = pkgs.writeText "vapidKeygen.exs" ''
-    [public_path, private_path] = System.argv()
-    {public_key, private_key} = :crypto.generate_key :ecdh, :prime256v1
-    File.write! public_path, Base.url_encode64(public_key, padding: false)
-    File.write! private_path, Base.url_encode64(private_key, padding: false)
-  '';
-
-  initSecretsScript = writeShell {
-    name = "akkoma-init-secrets";
-    runtimeInputs = with pkgs; [ coreutils cfg.package.elixirPackage ];
-    text = let
-      key-base = web.secret_key_base;
-      jwt-signer = ex.":joken".":default_signer";
-      signing-salt = web.signing_salt;
-      liveview-salt = web.live_view.signing_salt;
-      vapid-private = ex.":web_push_encryption".":vapid_details".private_key;
-      vapid-public = ex.":web_push_encryption".":vapid_details".public_key;
-    in ''
-      secret() {
-        # Generate default secret if non‐existent
-        test -e "$2" || install -D -m 0600 <(tr -dc 'A-Za-z-._~' </dev/urandom | head -c "$1") "$2"
-        if [ "$(stat --dereference --format='%s' "$2")" -lt "$1" ]; then
-          echo "Secret '$2' is smaller than minimum size of $1 bytes." >&2
-          exit 65
-        fi
-      }
-
-      secret 64 ${escapeShellArg key-base._secret}
-      secret 64 ${escapeShellArg jwt-signer._secret}
-      secret 8 ${escapeShellArg signing-salt._secret}
-      secret 8 ${escapeShellArg liveview-salt._secret}
-
-      ${optionalString (isSecret vapid-public) ''
-        { test -e ${escapeShellArg vapid-private._secret} && \
-          test -e ${escapeShellArg vapid-public._secret}; } || \
-            elixir ${escapeShellArgs [ vapidKeygen vapid-public._secret vapid-private._secret ]}
-      ''}
-    '';
-  };
-
-  configScript = writeShell {
-    name = "akkoma-config";
-    runtimeInputs = with pkgs; [ coreutils replace-secret ];
-    text = ''
-      cd "''${RUNTIME_DIRECTORY%%:*}"
-      tmp="$(mktemp config.exs.XXXXXXXXXX)"
-      trap 'rm -f "$tmp"' EXIT TERM
-
-      cat ${escapeShellArg configFile} >"$tmp"
-      ${concatMapStrings (file: ''
-        replace-secret ${escapeShellArgs [ (sha256 file) file ]} "$tmp"
-      '') secretPaths}
-
-      chown ${escapeShellArg cfg.user}:${escapeShellArg cfg.group} "$tmp"
-      chmod 0400 "$tmp"
-      mv -f "$tmp" config.exs
-    '';
-  };
-
-  pgpass = let
-    esc = escape [ ":" ''\'' ];
-  in if (cfg.initDb.password != null)
-    then pkgs.writeText "pgpass.conf" ''
-      *:*:*${esc cfg.initDb.username}:${esc (sha256 cfg.initDb.password._secret)}
-    ''
-    else null;
-
-  escapeSqlId = x: ''"${replaceStrings [ ''"'' ] [ ''""'' ] x}"'';
-  escapeSqlStr = x: "'${replaceStrings [ "'" ] [ "''" ] x}'";
-
-  setupSql = pkgs.writeText "setup.psql" ''
-    \set ON_ERROR_STOP on
-
-    ALTER ROLE ${escapeSqlId db.username}
-      LOGIN PASSWORD ${if db ? password
-        then "${escapeSqlStr (sha256 db.password._secret)}"
-        else "NULL"};
-
-    ALTER DATABASE ${escapeSqlId db.database}
-      OWNER TO ${escapeSqlId db.username};
-
-    \connect ${escapeSqlId db.database}
-    CREATE EXTENSION IF NOT EXISTS citext;
-    CREATE EXTENSION IF NOT EXISTS pg_trgm;
-    CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-  '';
-
-  dbHost = if db ? socket_dir then db.socket_dir
-    else if db ? socket then db.socket
-      else if db ? hostname then db.hostname
-        else null;
-
-  initDbScript = writeShell {
-    name = "akkoma-initdb";
-    runtimeInputs = with pkgs; [ coreutils replace-secret config.services.postgresql.package ];
-    text = ''
-      pgpass="$(mktemp -t pgpass-XXXXXXXXXX.conf)"
-      setupSql="$(mktemp -t setup-XXXXXXXXXX.psql)"
-      trap 'rm -f "$pgpass $setupSql"' EXIT TERM
-
-      ${optionalString (dbHost != null) ''
-        export PGHOST=${escapeShellArg dbHost}
-      ''}
-      export PGUSER=${escapeShellArg cfg.initDb.username}
-      ${optionalString (pgpass != null) ''
-        cat ${escapeShellArg pgpass} >"$pgpass"
-        replace-secret ${escapeShellArgs [
-          (sha256 cfg.initDb.password._secret) cfg.initDb.password._secret ]} "$pgpass"
-        export PGPASSFILE="$pgpass"
-      ''}
-
-      cat ${escapeShellArg setupSql} >"$setupSql"
-      ${optionalString (db ? password) ''
-        replace-secret ${escapeShellArgs [
-         (sha256 db.password._secret) db.password._secret ]} "$setupSql"
-      ''}
-
-      # Create role if non‐existent
-      psql -tAc "SELECT 1 FROM pg_roles
-        WHERE rolname = "${escapeShellArg (escapeSqlStr db.username)} | grep -F -q 1 || \
-        psql -tAc "CREATE ROLE "${escapeShellArg (escapeSqlId db.username)}
-
-      # Create database if non‐existent
-      psql -tAc "SELECT 1 FROM pg_database
-        WHERE datname = "${escapeShellArg (escapeSqlStr db.database)} | grep -F -q 1 || \
-        psql -tAc "CREATE DATABASE "${escapeShellArg (escapeSqlId db.database)}"
-          OWNER "${escapeShellArg (escapeSqlId db.username)}"
-          TEMPLATE template0
-          ENCODING 'utf8'
-          LOCALE 'C'"
-
-      psql -f "$setupSql"
-    '';
-  };
-
-  envWrapper = let
-    script = writeShell {
-      name = "akkoma-env";
-      text = ''
-        cd "${cfg.package}"
-
-        RUNTIME_DIRECTORY="''${RUNTIME_DIRECTORY:-/run/akkoma}"
-        AKKOMA_CONFIG_PATH="''${RUNTIME_DIRECTORY%%:*}/config.exs" \
-        ERL_EPMD_ADDRESS="${cfg.dist.address}" \
-        ERL_EPMD_PORT="${toString cfg.dist.epmdPort}" \
-        ERL_FLAGS=${lib.escapeShellArg (lib.escapeShellArgs ([
-          "-kernel" "inet_dist_use_interface" (erlAddr cfg.dist.address)
-          "-kernel" "inet_dist_listen_min" (toString cfg.dist.portMin)
-          "-kernel" "inet_dist_listen_max" (toString cfg.dist.portMax)
-        ] ++ cfg.dist.extraFlags))} \
-        RELEASE_COOKIE="$(<"''${RUNTIME_DIRECTORY%%:*}/cookie")" \
-        RELEASE_NAME="akkoma" \
-          exec "${cfg.package}/bin/$(basename "$0")" "$@"
-      '';
-    };
-  in pkgs.runCommandLocal "akkoma-env" { } ''
-    mkdir -p "$out/bin"
-
-    ln -r -s ${escapeShellArg script} "$out/bin/pleroma"
-    ln -r -s ${escapeShellArg script} "$out/bin/pleroma_ctl"
-  '';
-
-  userWrapper = pkgs.writeShellApplication {
-    name = "pleroma_ctl";
-    text = ''
-      if [ "''${1-}" == "update" ]; then
-        echo "OTP releases are not supported on NixOS." >&2
-        exit 64
-      fi
-
-      exec sudo -u ${escapeShellArg cfg.user} \
-        "${envWrapper}/bin/pleroma_ctl" "$@"
-    '';
-  };
-
-  socketScript = if isAbsolutePath web.http.ip
-    then writeShell {
-      name = "akkoma-socket";
-      runtimeInputs = with pkgs; [ coreutils inotify-tools ];
-      text = ''
-        coproc {
-          inotifywait -q -m -e create ${escapeShellArg (dirOf web.http.ip)}
-        }
-
-        trap 'kill "$COPROC_PID"' EXIT TERM
-
-        until test -S ${escapeShellArg web.http.ip}
-          do read -r -u "''${COPROC[0]}"
-        done
-
-        chmod 0666 ${escapeShellArg web.http.ip}
-      '';
-    }
-    else null;
-
-  staticDir = ex.":pleroma".":instance".static_dir;
-  uploadDir = ex.":pleroma".":instance".upload_dir;
-
-  staticFiles = pkgs.runCommandLocal "akkoma-static" { } ''
-    ${concatStringsSep "\n" (mapAttrsToList (key: val: ''
-      mkdir -p $out/frontends/${escapeShellArg val.name}/
-      ln -s ${escapeShellArg val.package} $out/frontends/${escapeShellArg val.name}/${escapeShellArg val.ref}
-    '') cfg.frontends)}
-
-    ${optionalString (cfg.extraStatic != null)
-      (concatStringsSep "\n" (mapAttrsToList (key: val: ''
-        mkdir -p "$out/$(dirname ${escapeShellArg key})"
-        ln -s ${escapeShellArg val} $out/${escapeShellArg key}
-      '') cfg.extraStatic))}
-  '';
-in {
-  options = {
-    services.akkoma = {
-      enable = mkEnableOption "Akkoma";
-
-      package = mkPackageOption pkgs "akkoma" { };
-
-      user = mkOption {
-        type = types.nonEmptyStr;
-        default = "akkoma";
-        description = "User account under which Akkoma runs.";
-      };
-
-      group = mkOption {
-        type = types.nonEmptyStr;
-        default = "akkoma";
-        description = "Group account under which Akkoma runs.";
-      };
-
-      initDb = {
-        enable = mkOption {
-          type = types.bool;
-          default = true;
-          description = ''
-            Whether to automatically initialise the database on startup. This will create a
-            database role and database if they do not already exist, and (re)set the role password
-            and the ownership of the database.
-
-            This setting can be used safely even if the database already exists and contains data.
-
-            The database settings are configured through
-            [{option}`config.services.akkoma.config.":pleroma"."Pleroma.Repo"`](#opt-services.akkoma.config.__pleroma_._Pleroma.Repo_).
-
-            If disabled, the database has to be set up manually:
-
-            ```SQL
-            CREATE ROLE akkoma LOGIN;
-
-            CREATE DATABASE akkoma
-              OWNER akkoma
-              TEMPLATE template0
-              ENCODING 'utf8'
-              LOCALE 'C';
-
-            \connect akkoma
-            CREATE EXTENSION IF NOT EXISTS citext;
-            CREATE EXTENSION IF NOT EXISTS pg_trgm;
-            CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-            ```
-          '';
-        };
-
-        username = mkOption {
-          type = types.nonEmptyStr;
-          default = config.services.postgresql.superUser;
-          defaultText = literalExpression "config.services.postgresql.superUser";
-          description = ''
-            Name of the database user to initialise the database with.
-
-            This user is required to have the `CREATEROLE` and `CREATEDB` capabilities.
-          '';
-        };
-
-        password = mkOption {
-          type = types.nullOr secret;
-          default = null;
-          description = ''
-            Password of the database user to initialise the database with.
-
-            If set to `null`, no password will be used.
-
-            The attribute `_secret` should point to a file containing the secret.
-          '';
-        };
-      };
-
-      initSecrets = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether to initialise non‐existent secrets with random values.
-
-          If enabled, appropriate secrets for the following options will be created automatically
-          if the files referenced in the `_secrets` attribute do not exist during startup.
-
-          - {option}`config.":pleroma"."Pleroma.Web.Endpoint".secret_key_base`
-          - {option}`config.":pleroma"."Pleroma.Web.Endpoint".signing_salt`
-          - {option}`config.":pleroma"."Pleroma.Web.Endpoint".live_view.signing_salt`
-          - {option}`config.":web_push_encryption".":vapid_details".private_key`
-          - {option}`config.":web_push_encryption".":vapid_details".public_key`
-          - {option}`config.":joken".":default_signer"`
-        '';
-      };
-
-      installWrapper = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether to install a wrapper around `pleroma_ctl` to simplify administration of the
-          Akkoma instance.
-        '';
-      };
-
-      extraPackages = mkOption {
-        type = with types; listOf package;
-        default = with pkgs; [ exiftool ffmpeg_5-headless graphicsmagick-imagemagick-compat ];
-        defaultText = literalExpression "with pkgs; [ exiftool graphicsmagick-imagemagick-compat ffmpeg_5-headless ]";
-        example = literalExpression "with pkgs; [ exiftool imagemagick ffmpeg_5-full ]";
-        description = ''
-          List of extra packages to include in the executable search path of the service unit.
-          These are needed by various configurable components such as:
-
-          - ExifTool for the `Pleroma.Upload.Filter.Exiftool` upload filter,
-          - ImageMagick for still image previews in the media proxy as well as for the
-            `Pleroma.Upload.Filters.Mogrify` upload filter, and
-          - ffmpeg for video previews in the media proxy.
-        '';
-      };
-
-      frontends = mkOption {
-        description = "Akkoma frontends.";
-        type = with types; attrsOf (submodule frontend);
-        default = {
-          primary = {
-            package = pkgs.akkoma-frontends.akkoma-fe;
-            name = "akkoma-fe";
-            ref = "stable";
-          };
-          admin = {
-            package = pkgs.akkoma-frontends.admin-fe;
-            name = "admin-fe";
-            ref = "stable";
-          };
-        };
-        defaultText = literalExpression ''
-          {
-            primary = {
-              package = pkgs.akkoma-frontends.akkoma-fe;
-              name = "akkoma-fe";
-              ref = "stable";
-            };
-            admin = {
-              package = pkgs.akkoma-frontends.admin-fe;
-              name = "admin-fe";
-              ref = "stable";
-            };
-          }
-        '';
-      };
-
-      extraStatic = mkOption {
-        type = with types; nullOr (attrsOf package);
-        description = ''
-          Attribute set of extra packages to add to the static files directory.
-
-          Do not add frontends here. These should be configured through
-          [{option}`services.akkoma.frontends`](#opt-services.akkoma.frontends).
-        '';
-        default = null;
-        example = literalExpression ''
-          {
-            "emoji/blobs.gg" = pkgs.akkoma-emoji.blobs_gg;
-            "static/terms-of-service.html" = pkgs.writeText "terms-of-service.html" '''
-              …
-            ''';
-            "favicon.png" = let
-              rev = "697a8211b0f427a921e7935a35d14bb3e32d0a2c";
-            in pkgs.stdenvNoCC.mkDerivation {
-              name = "favicon.png";
-
-              src = pkgs.fetchurl {
-                url = "https://raw.githubusercontent.com/TilCreator/NixOwO/''${rev}/NixOwO_plain.svg";
-                hash = "sha256-tWhHMfJ3Od58N9H5yOKPMfM56hYWSOnr/TGCBi8bo9E=";
-              };
-
-              nativeBuildInputs = with pkgs; [ librsvg ];
-
-              dontUnpack = true;
-              installPhase = '''
-                rsvg-convert -o $out -w 96 -h 96 $src
-              ''';
-            };
-          }
-        '';
-      };
-
-      dist = {
-        address = mkOption {
-          type = ipAddress;
-          default = "127.0.0.1";
-          description = ''
-            Listen address for Erlang distribution protocol and Port Mapper Daemon (epmd).
-          '';
-        };
-
-        epmdPort = mkOption {
-          type = types.port;
-          default = 4369;
-          description = "TCP port to bind Erlang Port Mapper Daemon to.";
-        };
-
-        extraFlags = mkOption {
-          type = with types; listOf str;
-          default = [ ];
-          description = "Extra flags to pass to Erlang";
-          example = [ "+sbwt" "none" "+sbwtdcpu" "none" "+sbwtdio" "none" ];
-        };
-
-        portMin = mkOption {
-          type = types.port;
-          default = 49152;
-          description = "Lower bound for Erlang distribution protocol TCP port.";
-        };
-
-        portMax = mkOption {
-          type = types.port;
-          default = 65535;
-          description = "Upper bound for Erlang distribution protocol TCP port.";
-        };
-
-        cookie = mkOption {
-          type = types.nullOr secret;
-          default = null;
-          example = { _secret = "/var/lib/secrets/akkoma/releaseCookie"; };
-          description = ''
-            Erlang release cookie.
-
-            If set to `null`, a temporary random cookie will be generated.
-          '';
-        };
-      };
-
-      config = mkOption {
-        description = ''
-          Configuration for Akkoma. The attributes are serialised to Elixir DSL.
-
-          Refer to <https://docs.akkoma.dev/stable/configuration/cheatsheet/> for
-          configuration options.
-
-          Settings containing secret data should be set to an attribute set containing the
-          attribute `_secret` - a string pointing to a file containing the value the option
-          should be set to.
-        '';
-        type = types.submodule {
-          freeformType = format.type;
-          options = {
-            ":pleroma" = {
-              ":instance" = {
-                name = mkOption {
-                  type = types.nonEmptyStr;
-                  description = "Instance name.";
-                };
-
-                email = mkOption {
-                  type = types.nonEmptyStr;
-                  description = "Instance administrator email.";
-                };
-
-                description = mkOption {
-                  type = types.nonEmptyStr;
-                  description = "Instance description.";
-                };
-
-                static_dir = mkOption {
-                  type = types.path;
-                  default = toString staticFiles;
-                  defaultText = literalMD ''
-                    Derivation gathering the following paths into a directory:
-
-                    - [{option}`services.akkoma.frontends`](#opt-services.akkoma.frontends)
-                    - [{option}`services.akkoma.extraStatic`](#opt-services.akkoma.extraStatic)
-                  '';
-                  description = ''
-                    Directory of static files.
-
-                    This directory can be built using a derivation, or it can be managed as mutable
-                    state by setting the option to an absolute path.
-                  '';
-                };
-
-                upload_dir = mkOption {
-                  type = absolutePath;
-                  default = "/var/lib/akkoma/uploads";
-                  description = ''
-                    Directory where Akkoma will put uploaded files.
-                  '';
-                };
-              };
-
-              "Pleroma.Repo" = mkOption {
-                type = elixirValue;
-                default = {
-                  adapter = format.lib.mkRaw "Ecto.Adapters.Postgres";
-                  socket_dir = "/run/postgresql";
-                  username = cfg.user;
-                  database = "akkoma";
-                };
-                defaultText = literalExpression ''
-                  {
-                    adapter = (pkgs.formats.elixirConf { }).lib.mkRaw "Ecto.Adapters.Postgres";
-                    socket_dir = "/run/postgresql";
-                    username = config.services.akkoma.user;
-                    database = "akkoma";
-                  }
-                '';
-                description = ''
-                  Database configuration.
-
-                  Refer to
-                  <https://hexdocs.pm/ecto_sql/Ecto.Adapters.Postgres.html#module-connection-options>
-                  for options.
-                '';
-              };
-
-              "Pleroma.Web.Endpoint" = {
-                url = {
-                  host = mkOption {
-                    type = types.nonEmptyStr;
-                    default = config.networking.fqdn;
-                    defaultText = literalExpression "config.networking.fqdn";
-                    description = "Domain name of the instance.";
-                  };
-
-                  scheme = mkOption {
-                    type = types.nonEmptyStr;
-                    default = "https";
-                    description = "URL scheme.";
-                  };
-
-                  port = mkOption {
-                    type = types.port;
-                    default = 443;
-                    description = "External port number.";
-                  };
-                };
-
-                http = {
-                  ip = mkOption {
-                    type = types.either absolutePath ipAddress;
-                    default = "/run/akkoma/socket";
-                    example = "::1";
-                    description = ''
-                      Listener IP address or Unix socket path.
-
-                      The value is automatically converted to Elixir’s internal address
-                      representation during serialisation.
-                    '';
-                  };
-
-                  port = mkOption {
-                    type = types.port;
-                    default = if isAbsolutePath web.http.ip then 0 else 4000;
-                    defaultText = literalExpression ''
-                      if isAbsolutePath config.services.akkoma.config.:pleroma"."Pleroma.Web.Endpoint".http.ip
-                        then 0
-                        else 4000;
-                    '';
-                    description = ''
-                      Listener port number.
-
-                      Must be 0 if using a Unix socket.
-                    '';
-                  };
-                };
-
-                secret_key_base = mkOption {
-                  type = secret;
-                  default = { _secret = "/var/lib/secrets/akkoma/key-base"; };
-                  description = ''
-                    Secret key used as a base to generate further secrets for encrypting and
-                    signing data.
-
-                    The attribute `_secret` should point to a file containing the secret.
-
-                    This key can generated can be generated as follows:
-
-                    ```ShellSession
-                    $ tr -dc 'A-Za-z-._~' </dev/urandom | head -c 64
-                    ```
-                  '';
-                };
-
-                live_view = {
-                  signing_salt = mkOption {
-                    type = secret;
-                    default = { _secret = "/var/lib/secrets/akkoma/liveview-salt"; };
-                    description = ''
-                      LiveView signing salt.
-
-                      The attribute `_secret` should point to a file containing the secret.
-
-                      This salt can be generated as follows:
-
-                      ```ShellSession
-                      $ tr -dc 'A-Za-z0-9-._~' </dev/urandom | head -c 8
-                      ```
-                    '';
-                  };
-                };
-
-                signing_salt = mkOption {
-                  type = secret;
-                  default = { _secret = "/var/lib/secrets/akkoma/signing-salt"; };
-                  description = ''
-                    Signing salt.
-
-                    The attribute `_secret` should point to a file containing the secret.
-
-                    This salt can be generated as follows:
-
-                    ```ShellSession
-                    $ tr -dc 'A-Za-z0-9-._~' </dev/urandom | head -c 8
-                    ```
-                  '';
-                };
-              };
-
-              "Pleroma.Upload" = let
-                httpConf = cfg.config.":pleroma"."Pleroma.Web.Endpoint".url;
-              in {
-                base_url = mkOption {
-                    type = types.nonEmptyStr;
-                    default = if lib.versionOlder config.system.stateVersion "24.05"
-                              then "${httpConf.scheme}://${httpConf.host}:${builtins.toString httpConf.port}/media/"
-                              else null;
-                    defaultText = literalExpression ''
-                      if lib.versionOlder config.system.stateVersion "24.05"
-                      then "$\{httpConf.scheme}://$\{httpConf.host}:$\{builtins.toString httpConf.port}/media/"
-                      else null;
-                    '';
-                    description = ''
-                      Base path which uploads will be stored at.
-                      Whilst this can just be set to a subdirectory of the main domain, it is now recommended to use a different subdomain.
-                    '';
-                };
-              };
-
-              ":frontends" = mkOption {
-                type = elixirValue;
-                default = mapAttrs
-                  (key: val: format.lib.mkMap { name = val.name; ref = val.ref; })
-                  cfg.frontends;
-                defaultText = literalExpression ''
-                  lib.mapAttrs (key: val:
-                    (pkgs.formats.elixirConf { }).lib.mkMap { name = val.name; ref = val.ref; })
-                    config.services.akkoma.frontends;
-                '';
-                description = ''
-                  Frontend configuration.
-
-                  Users should rely on the default value and prefer to configure frontends through
-                  [{option}`config.services.akkoma.frontends`](#opt-services.akkoma.frontends).
-                '';
-              };
-
-
-              ":media_proxy" = let
-                httpConf = cfg.config.":pleroma"."Pleroma.Web.Endpoint".url;
-              in {
-                enabled = mkOption {
-                    type = types.bool;
-                    default = false;
-                    defaultText = literalExpression "false";
-                    description = ''
-                      Whether to enable proxying of remote media through the instance's proxy.
-                    '';
-                };
-                base_url = mkOption {
-                    type = types.nullOr types.nonEmptyStr;
-                    default = if lib.versionOlder config.system.stateVersion "24.05"
-                              then "${httpConf.scheme}://${httpConf.host}:${builtins.toString httpConf.port}"
-                              else null;
-                    defaultText = literalExpression ''
-                      if lib.versionOlder config.system.stateVersion "24.05"
-                      then "$\{httpConf.scheme}://$\{httpConf.host}:$\{builtins.toString httpConf.port}"
-                      else null;
-                    '';
-                    description = ''
-                      Base path for the media proxy.
-                      Whilst this can just be set to a subdirectory of the main domain, it is now recommended to use a different subdomain.
-                    '';
-                };
-              };
-
-            };
-
-            ":web_push_encryption" = mkOption {
-              default = { };
-              description = ''
-                Web Push Notifications configuration.
-
-                The necessary key pair can be generated as follows:
-
-                ```ShellSession
-                $ nix-shell -p nodejs --run 'npx web-push generate-vapid-keys'
-                ```
-              '';
-              type = types.submodule {
-                freeformType = elixirValue;
-                options = {
-                  ":vapid_details" = {
-                    subject = mkOption {
-                      type = types.nonEmptyStr;
-                      default = "mailto:${ex.":pleroma".":instance".email}";
-                      defaultText = literalExpression ''
-                        "mailto:''${config.services.akkoma.config.":pleroma".":instance".email}"
-                      '';
-                      description = "mailto URI for administrative contact.";
-                    };
-
-                    public_key = mkOption {
-                      type = with types; either nonEmptyStr secret;
-                      default = { _secret = "/var/lib/secrets/akkoma/vapid-public"; };
-                      description = "base64-encoded public ECDH key.";
-                    };
-
-                    private_key = mkOption {
-                      type = secret;
-                      default = { _secret = "/var/lib/secrets/akkoma/vapid-private"; };
-                      description = ''
-                        base64-encoded private ECDH key.
-
-                        The attribute `_secret` should point to a file containing the secret.
-                      '';
-                    };
-                  };
-                };
-              };
-            };
-
-            ":joken" = {
-              ":default_signer" = mkOption {
-                type = secret;
-                default = { _secret = "/var/lib/secrets/akkoma/jwt-signer"; };
-                description = ''
-                  JWT signing secret.
-
-                  The attribute `_secret` should point to a file containing the secret.
-
-                  This secret can be generated as follows:
-
-                  ```ShellSession
-                  $ tr -dc 'A-Za-z0-9-._~' </dev/urandom | head -c 64
-                  ```
-                '';
-              };
-            };
-
-            ":logger" = {
-              ":backends" = mkOption {
-                type = types.listOf elixirValue;
-                visible = false;
-                default = with format.lib; [
-                  (mkTuple [ (mkRaw "ExSyslogger") (mkAtom ":ex_syslogger") ])
-                ];
-              };
-
-              ":ex_syslogger" = {
-                ident = mkOption {
-                  type = types.str;
-                  visible = false;
-                  default = "akkoma";
-                };
-
-                level = mkOption {
-                  type = types.nonEmptyStr;
-                  apply = format.lib.mkAtom;
-                  default = ":info";
-                  example = ":warning";
-                  description = ''
-                    Log level.
-
-                    Refer to
-                    <https://hexdocs.pm/logger/Logger.html#module-levels>
-                    for options.
-                  '';
-                };
-              };
-            };
-
-            ":tzdata" = {
-              ":data_dir" = mkOption {
-                type = elixirValue;
-                internal = true;
-                default = format.lib.mkRaw ''
-                  Path.join(System.fetch_env!("CACHE_DIRECTORY"), "tzdata")
-                '';
-              };
-            };
-          };
-        };
-      };
-
-      nginx = mkOption {
-        type = with types; nullOr (submodule
-          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }));
-        default = null;
-        description = ''
-          Extra configuration for the nginx virtual host of Akkoma.
-
-          If set to `null`, no virtual host will be added to the nginx configuration.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    assertions = optionals (cfg.config.":pleroma".":media_proxy".enabled && cfg.config.":pleroma".":media_proxy".base_url == null) [''
-      `services.akkoma.config.":pleroma".":media_proxy".base_url` must be set when the media proxy is enabled.
-    ''];
-    warnings = optionals (with config.security; cfg.installWrapper && (!sudo.enable) && (!sudo-rs.enable)) [''
-      The pleroma_ctl wrapper enabled by the installWrapper option relies on
-      sudo, which appears to have been disabled through security.sudo.enable.
-    ''];
-
-    users = {
-      users."${cfg.user}" = {
-        description = "Akkoma user";
-        group = cfg.group;
-        isSystemUser = true;
-      };
-      groups."${cfg.group}" = { };
-    };
-
-    # Confinement of the main service unit requires separation of the
-    # configuration generation into a separate unit to permit access to secrets
-    # residing outside of the chroot.
-    systemd.services.akkoma-config = {
-      description = "Akkoma social network configuration";
-      reloadTriggers = [ configFile ] ++ secretPaths;
-
-      unitConfig.PropagatesReloadTo = [ "akkoma.service" ];
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = true;
-        UMask = "0077";
-
-        RuntimeDirectory = mkBefore "akkoma";
-
-        ExecStart = mkMerge [
-          (mkIf (cfg.dist.cookie == null) [ genScript ])
-          (mkIf (cfg.dist.cookie != null) [ copyScript ])
-          (mkIf cfg.initSecrets [ initSecretsScript ])
-          [ configScript ]
-        ];
-
-        ExecReload = mkMerge [
-          (mkIf cfg.initSecrets [ initSecretsScript ])
-          [ configScript ]
-        ];
-      };
-    };
-
-    systemd.services.akkoma-initdb = mkIf cfg.initDb.enable {
-      description = "Akkoma social network database setup";
-      requires = [ "akkoma-config.service" ];
-      requiredBy = [ "akkoma.service" ];
-      after = [ "akkoma-config.service" "postgresql.service" ];
-      before = [ "akkoma.service" ];
-
-      serviceConfig = {
-        Type = "oneshot";
-        User = mkIf (db ? socket_dir || db ? socket)
-          cfg.initDb.username;
-        RemainAfterExit = true;
-        UMask = "0077";
-        ExecStart = initDbScript;
-        PrivateTmp = true;
-      };
-    };
-
-    systemd.services.akkoma = let
-      runtimeInputs = with pkgs; [ coreutils gawk gnused ] ++ cfg.extraPackages;
-    in {
-      description = "Akkoma social network";
-      documentation = [ "https://docs.akkoma.dev/stable/" ];
-
-      # This service depends on network-online.target and is sequenced after
-      # it because it requires access to the Internet to function properly.
-      bindsTo = [ "akkoma-config.service" ];
-      wants = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-      after = [
-        "akkoma-config.target"
-        "network.target"
-        "network-online.target"
-        "postgresql.service"
-      ];
-
-      confinement.packages = mkIf isConfined runtimeInputs;
-      path = runtimeInputs;
-
-      serviceConfig = {
-        Type = "exec";
-        User = cfg.user;
-        Group = cfg.group;
-        UMask = "0077";
-
-        # The run‐time directory is preserved as it is managed by the akkoma-config.service unit.
-        RuntimeDirectory = "akkoma";
-        RuntimeDirectoryPreserve = true;
-
-        CacheDirectory = "akkoma";
-
-        BindPaths = [ "${uploadDir}:${uploadDir}:norbind" ];
-        BindReadOnlyPaths = mkMerge [
-          (mkIf (!isStorePath staticDir) [ "${staticDir}:${staticDir}:norbind" ])
-          (mkIf isConfined (mkMerge [
-            [ "/etc/hosts" "/etc/resolv.conf" ]
-            (mkIf (isStorePath staticDir) (map (dir: "${dir}:${dir}:norbind")
-              (splitString "\n" (readFile ((pkgs.closureInfo { rootPaths = staticDir; }) + "/store-paths")))))
-            (mkIf (db ? socket_dir) [ "${db.socket_dir}:${db.socket_dir}:norbind" ])
-            (mkIf (db ? socket) [ "${db.socket}:${db.socket}:norbind" ])
-          ]))
-        ];
-
-        ExecStartPre = "${envWrapper}/bin/pleroma_ctl migrate";
-        ExecStart = "${envWrapper}/bin/pleroma start";
-        ExecStartPost = socketScript;
-        ExecStop = "${envWrapper}/bin/pleroma stop";
-        ExecStopPost = mkIf (isAbsolutePath web.http.ip)
-          "${pkgs.coreutils}/bin/rm -f '${web.http.ip}'";
-
-        ProtectProc = "noaccess";
-        ProcSubset = "pid";
-        ProtectSystem = "strict";
-        ProtectHome = true;
-        PrivateTmp = true;
-        PrivateDevices = true;
-        PrivateIPC = true;
-        ProtectHostname = true;
-        ProtectClock = true;
-        ProtectKernelTunables = true;
-        ProtectKernelModules = true;
-        ProtectKernelLogs = true;
-        ProtectControlGroups = true;
-
-        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
-        RestrictNamespaces = true;
-        LockPersonality = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        RemoveIPC = true;
-
-        CapabilityBoundingSet = mkIf
-          (any (port: port > 0 && port < 1024)
-            [ web.http.port cfg.dist.epmdPort cfg.dist.portMin ])
-          [ "CAP_NET_BIND_SERVICE" ];
-
-        NoNewPrivileges = true;
-        SystemCallFilter = [ "@system-service" "~@privileged" "@chown" ];
-        SystemCallArchitectures = "native";
-
-        DeviceAllow = null;
-        DevicePolicy = "closed";
-
-        # SMTP adapter uses dynamic port 0 binding, which is incompatible with bind address filtering
-        SocketBindAllow = mkIf (!hasSmtp) (mkMerge [
-          [ "tcp:${toString cfg.dist.epmdPort}" "tcp:${toString cfg.dist.portMin}-${toString cfg.dist.portMax}" ]
-          (mkIf (web.http.port != 0) [ "tcp:${toString web.http.port}" ])
-        ]);
-        SocketBindDeny = mkIf (!hasSmtp) "any";
-      };
-    };
-
-    systemd.tmpfiles.rules = [
-      "d ${uploadDir}  0700 ${cfg.user} ${cfg.group} - -"
-      "Z ${uploadDir} ~0700 ${cfg.user} ${cfg.group} - -"
-    ];
-
-    environment.systemPackages = mkIf (cfg.installWrapper) [ userWrapper ];
-
-    services.nginx.virtualHosts = mkIf (cfg.nginx != null) {
-      ${web.url.host} = mkMerge [ cfg.nginx {
-        locations."/" = {
-          proxyPass =
-            if isAbsolutePath web.http.ip
-              then "http://unix:${web.http.ip}"
-              else if hasInfix ":" web.http.ip
-                then "http://[${web.http.ip}]:${toString web.http.port}"
-                else "http://${web.http.ip}:${toString web.http.port}";
-
-          proxyWebsockets = true;
-          recommendedProxySettings = true;
-        };
-      }];
-    };
-  };
-
-  meta.maintainers = with maintainers; [ mvs ];
-  meta.doc = ./akkoma.md;
-}
diff --git a/nixos/modules/services/web-apps/alps.nix b/nixos/modules/services/web-apps/alps.nix
deleted file mode 100644
index e72b85eb3569..000000000000
--- a/nixos/modules/services/web-apps/alps.nix
+++ /dev/null
@@ -1,133 +0,0 @@
-{ lib, pkgs, config, ... }:
-
-with lib;
-
-let
-  cfg = config.services.alps;
-in {
-  options.services.alps = {
-    enable = mkEnableOption "alps";
-
-    port = mkOption {
-      type = types.port;
-      default = 1323;
-      description = ''
-        TCP port the service should listen on.
-      '';
-    };
-
-    bindIP = mkOption {
-      default = "[::]";
-      type = types.str;
-      description = ''
-        The IP the service should listen on.
-      '';
-    };
-
-    theme = mkOption {
-      type = types.enum [ "alps" "sourcehut" ];
-      default = "sourcehut";
-      description = ''
-        The frontend's theme to use.
-      '';
-    };
-
-    imaps = {
-      port = mkOption {
-        type = types.port;
-        default = 993;
-        description = ''
-          The IMAPS server port.
-        '';
-      };
-
-      host = mkOption {
-        type = types.str;
-        default = "[::1]";
-        example = "mail.example.org";
-        description = ''
-          The IMAPS server address.
-        '';
-      };
-    };
-
-    smtps = {
-      port = mkOption {
-        type = types.port;
-        default = 465;
-        description = ''
-          The SMTPS server port.
-        '';
-      };
-
-      host = mkOption {
-        type = types.str;
-        default = cfg.imaps.host;
-        defaultText = "services.alps.imaps.host";
-        example = "mail.example.org";
-        description = ''
-          The SMTPS server address.
-        '';
-      };
-    };
-
-    package = mkOption {
-      internal = true;
-      type = types.package;
-      default = pkgs.alps;
-    };
-
-    args = mkOption {
-      internal = true;
-      type = types.listOf types.str;
-      default = [
-        "-addr" "${cfg.bindIP}:${toString cfg.port}"
-        "-theme" "${cfg.theme}"
-        "imaps://${cfg.imaps.host}:${toString cfg.imaps.port}"
-        "smtps://${cfg.smtps.host}:${toString cfg.smtps.port}"
-      ];
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.alps = {
-      description = "alps is a simple and extensible webmail.";
-      documentation = [ "https://git.sr.ht/~migadu/alps" ];
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network.target" "network-online.target" ];
-
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/alps ${escapeShellArgs cfg.args}";
-        AmbientCapabilities = "";
-        CapabilityBoundingSet = "";
-        DynamicUser = true;
-        LockPersonality = true;
-        MemoryDenyWriteExecute = true;
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateIPC = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        ProtectSystem = "strict";
-        RemoveIPC = true;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SocketBindAllow = cfg.port;
-        SocketBindDeny = "any";
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged @obsolete" ];
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/anuko-time-tracker.nix b/nixos/modules/services/web-apps/anuko-time-tracker.nix
deleted file mode 100644
index 75f3d66b2f99..000000000000
--- a/nixos/modules/services/web-apps/anuko-time-tracker.nix
+++ /dev/null
@@ -1,388 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-  cfg = config.services.anuko-time-tracker;
-  configFile = let
-    smtpPassword = if cfg.settings.email.smtpPasswordFile == null
-                   then "''"
-                   else "trim(file_get_contents('${cfg.settings.email.smtpPasswordFile}'))";
-
-  in pkgs.writeText "config.php" ''
-    <?php
-    // Set include path for PEAR and its modules, which we include in the distribution.
-    // Updated for the correct location in the nix store.
-    set_include_path('${cfg.package}/WEB-INF/lib/pear' . PATH_SEPARATOR . get_include_path());
-    define('DSN', 'mysqli://${cfg.database.user}@${cfg.database.host}/${cfg.database.name}?charset=utf8mb4');
-    define('MULTIORG_MODE', ${lib.boolToString cfg.settings.multiorgMode});
-    define('EMAIL_REQUIRED', ${lib.boolToString cfg.settings.emailRequired});
-    define('WEEKEND_START_DAY', ${toString cfg.settings.weekendStartDay});
-    define('FORUM_LINK', '${cfg.settings.forumLink}');
-    define('HELP_LINK', '${cfg.settings.helpLink}');
-    define('SENDER', '${cfg.settings.email.sender}');
-    define('MAIL_MODE', '${cfg.settings.email.mode}');
-    define('MAIL_SMTP_HOST', '${toString cfg.settings.email.smtpHost}');
-    define('MAIL_SMTP_PORT', '${toString cfg.settings.email.smtpPort}');
-    define('MAIL_SMTP_USER', '${cfg.settings.email.smtpUser}');
-    define('MAIL_SMTP_PASSWORD', ${smtpPassword});
-    define('MAIL_SMTP_AUTH', ${lib.boolToString cfg.settings.email.smtpAuth});
-    define('MAIL_SMTP_DEBUG', ${lib.boolToString cfg.settings.email.smtpDebug});
-    define('DEFAULT_CSS', 'default.css');
-    define('RTL_CSS', 'rtl.css'); // For right to left languages.
-    define('LANG_DEFAULT', '${cfg.settings.defaultLanguage}');
-    define('CURRENCY_DEFAULT', '${cfg.settings.defaultCurrency}');
-    define('EXPORT_DECIMAL_DURATION', ${lib.boolToString cfg.settings.exportDecimalDuration});
-    define('REPORT_FOOTER', ${lib.boolToString cfg.settings.reportFooter});
-    define('AUTH_MODULE', 'db');
-  '';
-  package = pkgs.stdenv.mkDerivation rec {
-    pname = "anuko-time-tracker";
-    inherit (src) version;
-    src = cfg.package;
-    installPhase = ''
-      mkdir -p $out
-      cp -r * $out/
-
-      # Link config file
-      ln -s ${configFile} $out/WEB-INF/config.php
-
-      # Link writable templates_c directory
-      rm -rf $out/WEB-INF/templates_c
-      ln -s ${cfg.dataDir}/templates_c $out/WEB-INF/templates_c
-
-      # Remove unsafe dbinstall.php
-      rm -f $out/dbinstall.php
-    '';
-  };
-in
-{
-  options.services.anuko-time-tracker = {
-    enable = lib.mkEnableOption "Anuko Time Tracker";
-
-    package = lib.mkPackageOption pkgs "anuko-time-tracker" {};
-
-    database = {
-      createLocally = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = "Create the database and database user locally.";
-      };
-
-      host = lib.mkOption {
-        type = lib.types.str;
-        description = "Database host.";
-        default = "localhost";
-      };
-
-      name = lib.mkOption {
-        type = lib.types.str;
-        description = "Database name.";
-        default = "anuko_time_tracker";
-      };
-
-      user = lib.mkOption {
-        type = lib.types.str;
-        description = "Database username.";
-        default = "anuko_time_tracker";
-      };
-
-      passwordFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.str;
-        description = "Database user password file.";
-        default = null;
-      };
-    };
-
-    poolConfig = lib.mkOption {
-      type = lib.types.attrsOf (lib.types.oneOf [ lib.types.str lib.types.int lib.types.bool ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for Anuko Time Tracker's PHP-FPM pool.
-      '';
-    };
-
-    hostname = lib.mkOption {
-      type = lib.types.str;
-      default =
-        if config.networking.domain != null
-        then config.networking.fqdn
-        else config.networking.hostName;
-      defaultText = lib.literalExpression "config.networking.fqdn";
-      example = "anuko.example.com";
-      description = ''
-        The hostname to serve Anuko Time Tracker on.
-      '';
-    };
-
-    nginx = lib.mkOption {
-      type = lib.types.submodule (
-        lib.recursiveUpdate
-          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
-      );
-      default = {};
-      example = lib.literalExpression ''
-        {
-          serverAliases = [
-            "anuko.''${config.networking.domain}"
-          ];
-
-          # To enable encryption and let let's encrypt take care of certificate
-          forceSSL = true;
-          enableACME = true;
-        }
-      '';
-      description = ''
-        With this option, you can customize the Nginx virtualHost settings.
-      '';
-    };
-
-    dataDir = lib.mkOption {
-      type = lib.types.str;
-      default = "/var/lib/anuko-time-tracker";
-      description = "Default data folder for Anuko Time Tracker.";
-      example = "/mnt/anuko-time-tracker";
-    };
-
-    user = lib.mkOption {
-      type = lib.types.str;
-      default = "anuko_time_tracker";
-      description = "User under which Anuko Time Tracker runs.";
-    };
-
-    settings = {
-      multiorgMode = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = ''
-          Defines whether users see the Register option in the menu of Time Tracker that allows them
-          to self-register and create new organizations (top groups).
-        '';
-      };
-
-      emailRequired = lib.mkOption {
-        type = lib.types.bool;
-        default = false;
-        description = "Defines whether an email is required for new registrations.";
-      };
-
-      weekendStartDay = lib.mkOption {
-        type = lib.types.int;
-        default = 6;
-        description = ''
-          This option defines which days are highlighted with weekend color.
-          6 means Saturday. For Saudi Arabia, etc. set it to 4 for Thursday and Friday to be
-          weekend days.
-        '';
-      };
-
-      forumLink = lib.mkOption {
-        type = lib.types.str;
-        description = "Forum link from the main menu.";
-        default = "https://www.anuko.com/forum/viewforum.php?f=4";
-      };
-
-      helpLink = lib.mkOption {
-        type = lib.types.str;
-        description = "Help link from the main menu.";
-        default = "https://www.anuko.com/time-tracker/user-guide/index.htm";
-      };
-
-      email = {
-        sender = lib.mkOption {
-          type = lib.types.str;
-          description = "Default sender for mail.";
-          default = "Anuko Time Tracker <bounces@example.com>";
-        };
-
-        mode = lib.mkOption {
-          type = lib.types.str;
-          description = "Mail sending mode. Can be 'mail' or 'smtp'.";
-          default = "smtp";
-        };
-
-        smtpHost = lib.mkOption {
-          type = lib.types.str;
-          description = "MTA hostname.";
-          default = "localhost";
-        };
-
-        smtpPort = lib.mkOption {
-          type = lib.types.int;
-          description = "MTA port.";
-          default = 25;
-        };
-
-        smtpUser = lib.mkOption {
-          type = lib.types.str;
-          description = "MTA authentication username.";
-          default = "";
-        };
-
-        smtpAuth = lib.mkOption {
-          type = lib.types.bool;
-          default = false;
-          description = "MTA requires authentication.";
-        };
-
-        smtpPasswordFile = lib.mkOption {
-          type = lib.types.nullOr lib.types.path;
-          default = null;
-          example = "/var/lib/anuko-time-tracker/secrets/smtp-password";
-          description = ''
-            Path to file containing the MTA authentication password.
-          '';
-        };
-
-        smtpDebug = lib.mkOption {
-          type = lib.types.bool;
-          default = false;
-          description = "Debug mail sending.";
-        };
-      };
-
-      defaultLanguage = lib.mkOption {
-        type = lib.types.str;
-        description = ''
-          Defines Anuko Time Tracker default language. It is used on Time Tracker login page.
-          After login, a language set for user group is used.
-          Empty string means the language is defined by user browser.
-        '';
-        default = "";
-        example = "nl";
-      };
-
-      defaultCurrency = lib.mkOption {
-        type = lib.types.str;
-        description = ''
-          Defines a default currency symbol for new groups.
-          Use €, £, a more specific dollar like US$, CAD, etc.
-        '';
-        default = "$";
-        example = "€";
-      };
-
-      exportDecimalDuration = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = ''
-          Defines whether time duration values are decimal in CSV and XML data
-          exports (1.25 vs 1:15).
-        '';
-      };
-
-      reportFooter = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = "Defines whether to use a footer on reports.";
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-
-    assertions = [
-      {
-        assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
-        message = ''
-          <option>services.anuko-time-tracker.database.passwordFile</option> cannot be specified if
-          <option>services.anuko-time-tracker.database.createLocally</option> is set to true.
-        '';
-      }
-      {
-        assertion = cfg.settings.email.smtpAuth -> (cfg.settings.email.smtpPasswordFile != null);
-        message = ''
-          <option>services.anuko-time-tracker.settings.email.smtpPasswordFile</option> needs to be set if
-          <option>services.anuko-time-tracker.settings.email.smtpAuth</option> is enabled.
-        '';
-      }
-    ];
-
-    services.phpfpm = {
-      pools.anuko-time-tracker = {
-        inherit (cfg) user;
-        group = config.services.nginx.group;
-        settings = {
-          "listen.owner" = config.services.nginx.user;
-          "listen.group" = config.services.nginx.group;
-        } // cfg.poolConfig;
-      };
-    };
-
-    services.nginx = {
-      enable = lib.mkDefault true;
-      recommendedTlsSettings = true;
-      recommendedOptimisation = true;
-      recommendedGzipSettings = true;
-      virtualHosts."${cfg.hostname}" = lib.mkMerge [
-        cfg.nginx
-        {
-          root = lib.mkForce "${package}";
-          locations = {
-            "/".index = "index.php";
-            "~ [^/]\\.php(/|$)" = {
-              extraConfig = ''
-                fastcgi_split_path_info ^(.+?\.php)(/.*)$;
-                fastcgi_pass unix:${config.services.phpfpm.pools.anuko-time-tracker.socket};
-              '';
-            };
-          };
-        }
-      ];
-    };
-
-    services.mysql = lib.mkIf cfg.database.createLocally {
-      enable = lib.mkDefault true;
-      package = lib.mkDefault pkgs.mariadb;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [{
-        name = cfg.database.user;
-        ensurePermissions = {
-          "${cfg.database.name}.*" = "ALL PRIVILEGES";
-        };
-      }];
-    };
-
-    systemd = {
-      services = {
-        anuko-time-tracker-setup-database = lib.mkIf cfg.database.createLocally {
-          description = "Set up Anuko Time Tracker database";
-          serviceConfig = {
-            Type = "oneshot";
-            RemainAfterExit = true;
-          };
-          wantedBy = [ "phpfpm-anuko-time-tracker.service" ];
-          after = [ "mysql.service" ];
-          script =
-            let
-              mysql = "${config.services.mysql.package}/bin/mysql";
-            in
-            ''
-              if [ ! -f ${cfg.dataDir}/.dbexists ]; then
-                # Load database schema provided with package
-                ${mysql} ${cfg.database.name} < ${cfg.package}/mysql.sql
-
-                touch ${cfg.dataDir}/.dbexists
-              fi
-            '';
-        };
-      };
-      tmpfiles.rules = [
-        "d ${cfg.dataDir} 0750 ${cfg.user} ${config.services.nginx.group} -"
-        "d ${cfg.dataDir}/templates_c 0750 ${cfg.user} ${config.services.nginx.group} -"
-      ];
-    };
-
-    users.users."${cfg.user}" = {
-      isSystemUser = true;
-      group = config.services.nginx.group;
-    };
-  };
-
-  meta.maintainers = with lib.maintainers; [ michaelshmitty ];
-}
diff --git a/nixos/modules/services/web-apps/artalk.nix b/nixos/modules/services/web-apps/artalk.nix
deleted file mode 100644
index d3d06f1521b6..000000000000
--- a/nixos/modules/services/web-apps/artalk.nix
+++ /dev/null
@@ -1,131 +0,0 @@
-{
-  config,
-  lib,
-  pkgs,
-  utils,
-  ...
-}:
-let
-  cfg = config.services.artalk;
-  settingsFormat = pkgs.formats.json { };
-in
-{
-
-  meta = {
-    maintainers = with lib.maintainers; [ moraxyc ];
-  };
-
-  options = {
-    services.artalk = {
-      enable = lib.mkEnableOption "artalk, a comment system";
-      configFile = lib.mkOption {
-        type = lib.types.str;
-        default = "/etc/artalk/config.yml";
-        description = "Artalk config file path. If it is not exist, Artalk will generate one.";
-      };
-      allowModify = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = "allow Artalk store the settings to config file persistently";
-      };
-      workdir = lib.mkOption {
-        type = lib.types.str;
-        default = "/var/lib/artalk";
-        description = "Artalk working directory";
-      };
-      user = lib.mkOption {
-        type = lib.types.str;
-        default = "artalk";
-        description = "Artalk user name.";
-      };
-
-      group = lib.mkOption {
-        type = lib.types.str;
-        default = "artalk";
-        description = "Artalk group name.";
-      };
-
-      package = lib.mkPackageOption pkgs "artalk" { };
-      settings = lib.mkOption {
-        type = lib.types.submodule {
-          freeformType = settingsFormat.type;
-          options = {
-            host = lib.mkOption {
-              type = lib.types.str;
-              default = "0.0.0.0";
-              description = ''
-                Artalk server listen host
-              '';
-            };
-            port = lib.mkOption {
-              type = lib.types.port;
-              default = 23366;
-              description = ''
-                Artalk server listen port
-              '';
-            };
-          };
-        };
-        default = { };
-        description = ''
-          The artalk configuration.
-
-          If you set allowModify to true, Artalk will be able to store the settings in the config file persistently. This section's content will update in the config file after the service restarts.
-
-          Options containing secret data should be set to an attribute set
-          containing the attribute `_secret` - a string pointing to a file
-          containing the value the option should be set to.
-        '';
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    users.users.artalk = lib.optionalAttrs (cfg.user == "artalk") {
-      description = "artalk user";
-      isSystemUser = true;
-      group = cfg.group;
-    };
-    users.groups.artalk = lib.optionalAttrs (cfg.group == "artalk") { };
-
-    environment.systemPackages = [ cfg.package ];
-
-    systemd.services.artalk = {
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      preStart =
-        ''
-          umask 0077
-          ${utils.genJqSecretsReplacementSnippet cfg.settings "/run/artalk/new"}
-        ''
-        + (
-          if cfg.allowModify then
-            ''
-              [ -e "${cfg.configFile}" ] || ${lib.getExe cfg.package} gen config "${cfg.configFile}"
-              cat "${cfg.configFile}" | ${lib.getExe pkgs.yj} > "/run/artalk/old"
-              ${lib.getExe pkgs.jq} -s '.[0] * .[1]' "/run/artalk/old" "/run/artalk/new" > "/run/artalk/result"
-              cat "/run/artalk/result" | ${lib.getExe pkgs.yj} -r > "${cfg.configFile}"
-              rm /run/artalk/{old,new,result}
-            ''
-          else
-            ''
-              cat /run/artalk/new | ${lib.getExe pkgs.yj} -r > "${cfg.configFile}"
-              rm /run/artalk/new
-            ''
-        );
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        Type = "simple";
-        ExecStart = "${lib.getExe cfg.package} server --config ${cfg.configFile} --workdir ${cfg.workdir} --host ${cfg.settings.host} --port ${builtins.toString cfg.settings.port}";
-        Restart = "on-failure";
-        RestartSec = "5s";
-        ConfigurationDirectory = [ "artalk" ];
-        StateDirectory = [ "artalk" ];
-        RuntimeDirectory = [ "artalk" ];
-        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
-        ProtectHome = "yes";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixos/modules/services/web-apps/atlassian/confluence.nix
deleted file mode 100644
index 683a1c7603ef..000000000000
--- a/nixos/modules/services/web-apps/atlassian/confluence.nix
+++ /dev/null
@@ -1,224 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.confluence;
-
-  pkg = cfg.package.override (optionalAttrs cfg.sso.enable {
-    enableSSO = cfg.sso.enable;
-  });
-
-  crowdProperties = pkgs.writeText "crowd.properties" ''
-    application.name                        ${cfg.sso.applicationName}
-    application.password                    ${if cfg.sso.applicationPassword != null then cfg.sso.applicationPassword else "@NIXOS_CONFLUENCE_CROWD_SSO_PWD@"}
-    application.login.url                   ${cfg.sso.crowd}/console/
-
-    crowd.server.url                        ${cfg.sso.crowd}/services/
-    crowd.base.url                          ${cfg.sso.crowd}/
-
-    session.isauthenticated                 session.isauthenticated
-    session.tokenkey                        session.tokenkey
-    session.validationinterval              ${toString cfg.sso.validationInterval}
-    session.lastvalidation                  session.lastvalidation
-  '';
-
-in
-
-{
-  options = {
-    services.confluence = {
-      enable = mkEnableOption "Atlassian Confluence service";
-
-      user = mkOption {
-        type = types.str;
-        default = "confluence";
-        description = "User which runs confluence.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "confluence";
-        description = "Group which runs confluence.";
-      };
-
-      home = mkOption {
-        type = types.str;
-        default = "/var/lib/confluence";
-        description = "Home directory of the confluence instance.";
-      };
-
-      listenAddress = mkOption {
-        type = types.str;
-        default = "127.0.0.1";
-        description = "Address to listen on.";
-      };
-
-      listenPort = mkOption {
-        type = types.port;
-        default = 8090;
-        description = "Port to listen on.";
-      };
-
-      catalinaOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "-Xms1024m" "-Xmx2048m" "-Dconfluence.disable.peopledirectory.all=true" ];
-        description = "Java options to pass to catalina/tomcat.";
-      };
-
-      proxy = {
-        enable = mkEnableOption "proxy support";
-
-        name = mkOption {
-          type = types.str;
-          example = "confluence.example.com";
-          description = "Virtual hostname at the proxy";
-        };
-
-        port = mkOption {
-          type = types.port;
-          default = 443;
-          example = 80;
-          description = "Port used at the proxy";
-        };
-
-        scheme = mkOption {
-          type = types.str;
-          default = "https";
-          example = "http";
-          description = "Protocol used at the proxy.";
-        };
-      };
-
-      sso = {
-        enable = mkEnableOption "SSO with Atlassian Crowd";
-
-        crowd = mkOption {
-          type = types.str;
-          example = "http://localhost:8095/crowd";
-          description = "Crowd Base URL without trailing slash";
-        };
-
-        applicationName = mkOption {
-          type = types.str;
-          example = "jira";
-          description = "Exact name of this Confluence instance in Crowd";
-        };
-
-        applicationPassword = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          description = "Application password of this Confluence instance in Crowd";
-        };
-
-        applicationPasswordFile = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          description = "Path to the application password for Crowd of Confluence.";
-        };
-
-        validationInterval = mkOption {
-          type = types.int;
-          default = 2;
-          example = 0;
-          description = ''
-            Set to 0, if you want authentication checks to occur on each
-            request. Otherwise set to the number of minutes between request
-            to validate if the user is logged in or out of the Crowd SSO
-            server. Setting this value to 1 or higher will increase the
-            performance of Crowd's integration.
-          '';
-        };
-      };
-
-      package = mkPackageOption pkgs "atlassian-confluence" { };
-
-      jrePackage = mkPackageOption pkgs "oraclejre8" {
-        extraDescription = ''
-        ::: {.note }
-        Atlassian only supports the Oracle JRE (JRASERVER-46152).
-        :::
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    users.users.${cfg.user} = {
-      isSystemUser = true;
-      group = cfg.group;
-    };
-
-    assertions = [
-      { assertion = cfg.sso.enable -> ((cfg.sso.applicationPassword == null) != (cfg.sso.applicationPasswordFile));
-        message = "Please set either applicationPassword or applicationPasswordFile";
-      }
-    ];
-
-    warnings = mkIf (cfg.sso.enable && cfg.sso.applicationPassword != null) [
-      "Using `services.confluence.sso.applicationPassword` is deprecated! Use `applicationPasswordFile` instead!"
-    ];
-
-    users.groups.${cfg.group} = {};
-
-    systemd.tmpfiles.rules = [
-      "d '${cfg.home}' - ${cfg.user} - - -"
-      "d /run/confluence - - - - -"
-
-      "L+ /run/confluence/home - - - - ${cfg.home}"
-      "L+ /run/confluence/logs - - - - ${cfg.home}/logs"
-      "L+ /run/confluence/temp - - - - ${cfg.home}/temp"
-      "L+ /run/confluence/work - - - - ${cfg.home}/work"
-      "L+ /run/confluence/server.xml - - - - ${cfg.home}/server.xml"
-    ];
-
-    systemd.services.confluence = {
-      description = "Atlassian Confluence";
-
-      wantedBy = [ "multi-user.target" ];
-      requires = [ "postgresql.service" ];
-      after = [ "postgresql.service" ];
-
-      path = [ cfg.jrePackage pkgs.bash ];
-
-      environment = {
-        CONF_USER = cfg.user;
-        JAVA_HOME = "${cfg.jrePackage}";
-        CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
-        JAVA_OPTS = mkIf cfg.sso.enable "-Dcrowd.properties=${cfg.home}/crowd.properties";
-      };
-
-      preStart = ''
-        mkdir -p ${cfg.home}/{logs,work,temp,deploy}
-
-        sed -e 's,port="8090",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
-        '' + (lib.optionalString cfg.proxy.enable ''
-          -e 's,protocol="org.apache.coyote.http11.Http11NioProtocol",protocol="org.apache.coyote.http11.Http11NioProtocol" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}",' \
-        '') + ''
-          ${pkg}/conf/server.xml.dist > ${cfg.home}/server.xml
-
-        ${optionalString cfg.sso.enable ''
-          install -m660 ${crowdProperties} ${cfg.home}/crowd.properties
-          ${optionalString (cfg.sso.applicationPasswordFile != null) ''
-            ${pkgs.replace-secret}/bin/replace-secret \
-              '@NIXOS_CONFLUENCE_CROWD_SSO_PWD@' \
-              ${cfg.sso.applicationPasswordFile} \
-              ${cfg.home}/crowd.properties
-          ''}
-        ''}
-      '';
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        PrivateTmp = true;
-        Restart = "on-failure";
-        RestartSec = "10";
-        ExecStart = "${pkg}/bin/start-confluence.sh -fg";
-        ExecStop = "${pkg}/bin/stop-confluence.sh";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixos/modules/services/web-apps/atlassian/crowd.nix
deleted file mode 100644
index 527fa1743df2..000000000000
--- a/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ /dev/null
@@ -1,193 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.crowd;
-
-  pkg = cfg.package.override {
-    home = cfg.home;
-    port = cfg.listenPort;
-    openidPassword = cfg.openidPassword;
-  } // (optionalAttrs cfg.proxy.enable {
-    proxyUrl = "${cfg.proxy.scheme}://${cfg.proxy.name}:${toString cfg.proxy.port}";
-  });
-
-  crowdPropertiesFile = pkgs.writeText "crowd.properties" ''
-    application.name                        crowd-openid-server
-    application.password @NIXOS_CROWD_OPENID_PW@
-    application.base.url                    http://localhost:${toString cfg.listenPort}/openidserver
-    application.login.url                   http://localhost:${toString cfg.listenPort}/openidserver
-    application.login.url.template          http://localhost:${toString cfg.listenPort}/openidserver?returnToUrl=''${RETURN_TO_URL}
-
-    crowd.server.url                        http://localhost:${toString cfg.listenPort}/crowd/services/
-
-    session.isauthenticated                 session.isauthenticated
-    session.tokenkey                        session.tokenkey
-    session.validationinterval              0
-    session.lastvalidation                  session.lastvalidation
-  '';
-
-in
-
-{
-  options = {
-    services.crowd = {
-      enable = mkEnableOption "Atlassian Crowd service";
-
-      user = mkOption {
-        type = types.str;
-        default = "crowd";
-        description = "User which runs Crowd.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "crowd";
-        description = "Group which runs Crowd.";
-      };
-
-      home = mkOption {
-        type = types.str;
-        default = "/var/lib/crowd";
-        description = "Home directory of the Crowd instance.";
-      };
-
-      listenAddress = mkOption {
-        type = types.str;
-        default = "127.0.0.1";
-        description = "Address to listen on.";
-      };
-
-      listenPort = mkOption {
-        type = types.port;
-        default = 8092;
-        description = "Port to listen on.";
-      };
-
-      openidPassword = mkOption {
-        type = types.str;
-        default = "WILL_NEVER_BE_SET";
-        description = "Application password for OpenID server.";
-      };
-
-      openidPasswordFile = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = "Path to the file containing the application password for OpenID server.";
-      };
-
-      catalinaOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "-Xms1024m" "-Xmx2048m" ];
-        description = "Java options to pass to catalina/tomcat.";
-      };
-
-      proxy = {
-        enable = mkEnableOption "reverse proxy support";
-
-        name = mkOption {
-          type = types.str;
-          example = "crowd.example.com";
-          description = "Virtual hostname at the proxy";
-        };
-
-        port = mkOption {
-          type = types.port;
-          default = 443;
-          example = 80;
-          description = "Port used at the proxy";
-        };
-
-        scheme = mkOption {
-          type = types.str;
-          default = "https";
-          example = "http";
-          description = "Protocol used at the proxy.";
-        };
-
-        secure = mkOption {
-          type = types.bool;
-          default = true;
-          description = "Whether the connections to the proxy should be considered secure.";
-        };
-      };
-
-      package = mkPackageOption pkgs "atlassian-crowd" { };
-
-      jrePackage = mkPackageOption pkgs "oraclejre8" {
-        extraDescription = ''
-        ::: {.note }
-        Atlassian only supports the Oracle JRE (JRASERVER-46152).
-        :::
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    users.users.${cfg.user} = {
-      isSystemUser = true;
-      group = cfg.group;
-    };
-
-    users.groups.${cfg.group} = {};
-
-    systemd.tmpfiles.rules = [
-      "d '${cfg.home}' - ${cfg.user} ${cfg.group} - -"
-      "d /run/atlassian-crowd - - - - -"
-
-      "L+ /run/atlassian-crowd/database - - - - ${cfg.home}/database"
-      "L+ /run/atlassian-crowd/logs - - - - ${cfg.home}/logs"
-      "L+ /run/atlassian-crowd/work - - - - ${cfg.home}/work"
-      "L+ /run/atlassian-crowd/server.xml - - - - ${cfg.home}/server.xml"
-    ];
-
-    systemd.services.atlassian-crowd = {
-      description = "Atlassian Crowd";
-
-      wantedBy = [ "multi-user.target" ];
-      requires = [ "postgresql.service" ];
-      after = [ "postgresql.service" ];
-
-      path = [ cfg.jrePackage ];
-
-      environment = {
-        JAVA_HOME = "${cfg.jrePackage}";
-        CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
-        CATALINA_TMPDIR = "/tmp";
-        JAVA_OPTS = mkIf (cfg.openidPasswordFile != null) "-Dcrowd.properties=${cfg.home}/crowd.properties";
-      };
-
-      preStart = ''
-        rm -rf ${cfg.home}/work
-        mkdir -p ${cfg.home}/{logs,database,work}
-
-        sed -e 's,port="8095",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
-        '' + (lib.optionalString cfg.proxy.enable ''
-          -e 's,compression="on",compression="off" protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${boolToString cfg.proxy.secure}",' \
-        '') + ''
-          ${pkg}/apache-tomcat/conf/server.xml.dist > ${cfg.home}/server.xml
-
-        ${optionalString (cfg.openidPasswordFile != null) ''
-          install -m660 ${crowdPropertiesFile} ${cfg.home}/crowd.properties
-          ${pkgs.replace-secret}/bin/replace-secret \
-            '@NIXOS_CROWD_OPENID_PW@' \
-            ${cfg.openidPasswordFile} \
-            ${cfg.home}/crowd.properties
-        ''}
-      '';
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        PrivateTmp = true;
-        Restart = "on-failure";
-        RestartSec = "10";
-        ExecStart = "${pkg}/start_crowd.sh -fg";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/atlassian/jira.nix b/nixos/modules/services/web-apps/atlassian/jira.nix
deleted file mode 100644
index 40c5d95cae3a..000000000000
--- a/nixos/modules/services/web-apps/atlassian/jira.nix
+++ /dev/null
@@ -1,219 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.jira;
-
-  pkg = cfg.package.override (optionalAttrs cfg.sso.enable {
-    enableSSO = cfg.sso.enable;
-  });
-
-  crowdProperties = pkgs.writeText "crowd.properties" ''
-    application.name                        ${cfg.sso.applicationName}
-    application.password                    @NIXOS_JIRA_CROWD_SSO_PWD@
-    application.login.url                   ${cfg.sso.crowd}/console/
-
-    crowd.server.url                        ${cfg.sso.crowd}/services/
-    crowd.base.url                          ${cfg.sso.crowd}/
-
-    session.isauthenticated                 session.isauthenticated
-    session.tokenkey                        session.tokenkey
-    session.validationinterval              ${toString cfg.sso.validationInterval}
-    session.lastvalidation                  session.lastvalidation
-  '';
-
-in
-
-{
-  options = {
-    services.jira = {
-      enable = mkEnableOption "Atlassian JIRA service";
-
-      user = mkOption {
-        type = types.str;
-        default = "jira";
-        description = "User which runs JIRA.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "jira";
-        description = "Group which runs JIRA.";
-      };
-
-      home = mkOption {
-        type = types.str;
-        default = "/var/lib/jira";
-        description = "Home directory of the JIRA instance.";
-      };
-
-      listenAddress = mkOption {
-        type = types.str;
-        default = "127.0.0.1";
-        description = "Address to listen on.";
-      };
-
-      listenPort = mkOption {
-        type = types.port;
-        default = 8091;
-        description = "Port to listen on.";
-      };
-
-      catalinaOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "-Xms1024m" "-Xmx2048m" ];
-        description = "Java options to pass to catalina/tomcat.";
-      };
-
-      proxy = {
-        enable = mkEnableOption "reverse proxy support";
-
-        name = mkOption {
-          type = types.str;
-          example = "jira.example.com";
-          description = "Virtual hostname at the proxy";
-        };
-
-        port = mkOption {
-          type = types.port;
-          default = 443;
-          example = 80;
-          description = "Port used at the proxy";
-        };
-
-        scheme = mkOption {
-          type = types.str;
-          default = "https";
-          example = "http";
-          description = "Protocol used at the proxy.";
-        };
-
-        secure = mkOption {
-          type = types.bool;
-          default = true;
-          description = "Whether the connections to the proxy should be considered secure.";
-        };
-      };
-
-      sso = {
-        enable = mkEnableOption "SSO with Atlassian Crowd";
-
-        crowd = mkOption {
-          type = types.str;
-          example = "http://localhost:8095/crowd";
-          description = "Crowd Base URL without trailing slash";
-        };
-
-        applicationName = mkOption {
-          type = types.str;
-          example = "jira";
-          description = "Exact name of this JIRA instance in Crowd";
-        };
-
-        applicationPasswordFile = mkOption {
-          type = types.str;
-          description = "Path to the file containing the application password of this JIRA instance in Crowd";
-        };
-
-        validationInterval = mkOption {
-          type = types.int;
-          default = 2;
-          example = 0;
-          description = ''
-            Set to 0, if you want authentication checks to occur on each
-            request. Otherwise set to the number of minutes between request
-            to validate if the user is logged in or out of the Crowd SSO
-            server. Setting this value to 1 or higher will increase the
-            performance of Crowd's integration.
-          '';
-        };
-      };
-
-      package = mkPackageOption pkgs "atlassian-jira" { };
-
-      jrePackage = mkPackageOption pkgs "oraclejre8" {
-        extraDescription = ''
-        ::: {.note }
-        Atlassian only supports the Oracle JRE (JRASERVER-46152).
-        :::
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    users.users.${cfg.user} = {
-      isSystemUser = true;
-      group = cfg.group;
-      home = cfg.home;
-    };
-
-    users.groups.${cfg.group} = {};
-
-    systemd.tmpfiles.rules = [
-      "d '${cfg.home}' - ${cfg.user} - - -"
-      "d /run/atlassian-jira - - - - -"
-
-      "L+ /run/atlassian-jira/home - - - - ${cfg.home}"
-      "L+ /run/atlassian-jira/logs - - - - ${cfg.home}/logs"
-      "L+ /run/atlassian-jira/work - - - - ${cfg.home}/work"
-      "L+ /run/atlassian-jira/temp - - - - ${cfg.home}/temp"
-      "L+ /run/atlassian-jira/server.xml - - - - ${cfg.home}/server.xml"
-    ];
-
-    systemd.services.atlassian-jira = {
-      description = "Atlassian JIRA";
-
-      wantedBy = [ "multi-user.target" ];
-      requires = [ "postgresql.service" ];
-      after = [ "postgresql.service" ];
-
-      path = [ cfg.jrePackage pkgs.bash ];
-
-      environment = {
-        JIRA_USER = cfg.user;
-        JIRA_HOME = cfg.home;
-        JAVA_HOME = "${cfg.jrePackage}";
-        CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
-        JAVA_OPTS = mkIf cfg.sso.enable "-Dcrowd.properties=${cfg.home}/crowd.properties";
-      };
-
-      preStart = ''
-        mkdir -p ${cfg.home}/{logs,work,temp,deploy}
-
-        sed -e 's,port="8080",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
-        '' + (lib.optionalString cfg.proxy.enable ''
-          -e 's,protocol="HTTP/1.1",protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${toString cfg.proxy.secure}",' \
-        '') + ''
-          ${pkg}/conf/server.xml.dist > ${cfg.home}/server.xml
-
-        ${optionalString cfg.sso.enable ''
-          install -m660 ${crowdProperties} ${cfg.home}/crowd.properties
-          ${pkgs.replace-secret}/bin/replace-secret \
-            '@NIXOS_JIRA_CROWD_SSO_PWD@' \
-            ${cfg.sso.applicationPasswordFile} \
-            ${cfg.home}/crowd.properties
-        ''}
-      '';
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        PrivateTmp = true;
-        Restart = "on-failure";
-        RestartSec = "10";
-        ExecStart = "${pkg}/bin/start-jira.sh -fg";
-        ExecStop = "${pkg}/bin/stop-jira.sh";
-      };
-    };
-  };
-
-  imports = [
-    (mkRemovedOptionModule [ "services" "jira" "sso" "applicationPassword" ] ''
-      Use `applicationPasswordFile` instead!
-    '')
-  ];
-}
diff --git a/nixos/modules/services/web-apps/audiobookshelf.nix b/nixos/modules/services/web-apps/audiobookshelf.nix
deleted file mode 100644
index 84dffc5f9d3c..000000000000
--- a/nixos/modules/services/web-apps/audiobookshelf.nix
+++ /dev/null
@@ -1,90 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.audiobookshelf;
-in
-{
-  options = {
-    services.audiobookshelf = {
-      enable = mkEnableOption "Audiobookshelf, self-hosted audiobook and podcast server.";
-
-      package = mkPackageOption pkgs "audiobookshelf" { };
-
-      dataDir = mkOption {
-        description = "Path to Audiobookshelf config and metadata inside of /var/lib.";
-        default = "audiobookshelf";
-        type = types.str;
-      };
-
-      host = mkOption {
-        description = "The host Audiobookshelf binds to.";
-        default = "127.0.0.1";
-        example = "0.0.0.0";
-        type = types.str;
-      };
-
-      port = mkOption {
-        description = "The TCP port Audiobookshelf will listen on.";
-        default = 8000;
-        type = types.port;
-      };
-
-      user = mkOption {
-        description = "User account under which Audiobookshelf runs.";
-        default = "audiobookshelf";
-        type = types.str;
-      };
-
-      group = mkOption {
-        description = "Group under which Audiobookshelf runs.";
-        default = "audiobookshelf";
-        type = types.str;
-      };
-
-      openFirewall = mkOption {
-        description = "Open ports in the firewall for the Audiobookshelf web interface.";
-        default = false;
-        type = types.bool;
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.audiobookshelf = {
-      description = "Audiobookshelf is a self-hosted audiobook and podcast server";
-
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        Type = "simple";
-        User = cfg.user;
-        Group = cfg.group;
-        StateDirectory = cfg.dataDir;
-        WorkingDirectory = "/var/lib/${cfg.dataDir}";
-        ExecStart = "${cfg.package}/bin/audiobookshelf --host ${cfg.host} --port ${toString cfg.port}";
-        Restart = "on-failure";
-      };
-    };
-
-    users.users = mkIf (cfg.user == "audiobookshelf") {
-      audiobookshelf = {
-        isSystemUser = true;
-        group = cfg.group;
-        home = "/var/lib/${cfg.dataDir}";
-      };
-    };
-
-    users.groups = mkIf (cfg.group == "audiobookshelf") {
-      audiobookshelf = { };
-    };
-
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.port ];
-    };
-  };
-
-  meta.maintainers = with maintainers; [ wietsedv ];
-}
diff --git a/nixos/modules/services/web-apps/bookstack.nix b/nixos/modules/services/web-apps/bookstack.nix
deleted file mode 100644
index 21948fd310d6..000000000000
--- a/nixos/modules/services/web-apps/bookstack.nix
+++ /dev/null
@@ -1,451 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.bookstack;
-  bookstack = pkgs.bookstack.override {
-    dataDir = cfg.dataDir;
-  };
-  db = cfg.database;
-  mail = cfg.mail;
-
-  user = cfg.user;
-  group = cfg.group;
-
-  # shell script for local administration
-  artisan = pkgs.writeScriptBin "bookstack" ''
-    #! ${pkgs.runtimeShell}
-    cd ${bookstack}
-    sudo=exec
-    if [[ "$USER" != ${user} ]]; then
-      sudo='exec /run/wrappers/bin/sudo -u ${user}'
-    fi
-    $sudo ${pkgs.php}/bin/php artisan $*
-  '';
-
-  tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME;
-
-in {
-  imports = [
-    (mkRemovedOptionModule [ "services" "bookstack" "extraConfig" ] "Use services.bookstack.config instead.")
-    (mkRemovedOptionModule [ "services" "bookstack" "cacheDir" ] "The cache directory is now handled automatically.")
-  ];
-
-  options.services.bookstack = {
-
-    enable = mkEnableOption "BookStack";
-
-    user = mkOption {
-      default = "bookstack";
-      description = "User bookstack runs as.";
-      type = types.str;
-    };
-
-    group = mkOption {
-      default = "bookstack";
-      description = "Group bookstack runs as.";
-      type = types.str;
-    };
-
-    appKeyFile = mkOption {
-      description = ''
-        A file containing the Laravel APP_KEY - a 32 character long,
-        base64 encoded key used for encryption where needed. Can be
-        generated with `head -c 32 /dev/urandom | base64`.
-      '';
-      example = "/run/keys/bookstack-appkey";
-      type = types.path;
-    };
-
-    hostname = lib.mkOption {
-      type = lib.types.str;
-      default = config.networking.fqdnOrHostName;
-      defaultText = lib.literalExpression "config.networking.fqdnOrHostName";
-      example = "bookstack.example.com";
-      description = ''
-        The hostname to serve BookStack on.
-      '';
-    };
-
-    appURL = mkOption {
-      description = ''
-        The root URL that you want to host BookStack on. All URLs in BookStack will be generated using this value.
-        If you change this in the future you may need to run a command to update stored URLs in the database. Command example: `php artisan bookstack:update-url https://old.example.com https://new.example.com`
-      '';
-      default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
-      defaultText = ''http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostname}'';
-      example = "https://example.com";
-      type = types.str;
-    };
-
-    dataDir = mkOption {
-      description = "BookStack data directory";
-      default = "/var/lib/bookstack";
-      type = types.path;
-    };
-
-    database = {
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Database host address.";
-      };
-      port = mkOption {
-        type = types.port;
-        default = 3306;
-        description = "Database host port.";
-      };
-      name = mkOption {
-        type = types.str;
-        default = "bookstack";
-        description = "Database name.";
-      };
-      user = mkOption {
-        type = types.str;
-        default = user;
-        defaultText = literalExpression "user";
-        description = "Database username.";
-      };
-      passwordFile = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        example = "/run/keys/bookstack-dbpassword";
-        description = ''
-          A file containing the password corresponding to
-          {option}`database.user`.
-        '';
-      };
-      createLocally = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Create the database and database user locally.";
-      };
-    };
-
-    mail = {
-      driver = mkOption {
-        type = types.enum [ "smtp" "sendmail" ];
-        default = "smtp";
-        description = "Mail driver to use.";
-      };
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Mail host address.";
-      };
-      port = mkOption {
-        type = types.port;
-        default = 1025;
-        description = "Mail host port.";
-      };
-      fromName = mkOption {
-        type = types.str;
-        default = "BookStack";
-        description = "Mail \"from\" name.";
-      };
-      from = mkOption {
-        type = types.str;
-        default = "mail@bookstackapp.com";
-        description = "Mail \"from\" email.";
-      };
-      user = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        example = "bookstack";
-        description = "Mail username.";
-      };
-      passwordFile = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        example = "/run/keys/bookstack-mailpassword";
-        description = ''
-          A file containing the password corresponding to
-          {option}`mail.user`.
-        '';
-      };
-      encryption = mkOption {
-        type = with types; nullOr (enum [ "tls" ]);
-        default = null;
-        description = "SMTP encryption mechanism to use.";
-      };
-    };
-
-    maxUploadSize = mkOption {
-      type = types.str;
-      default = "18M";
-      example = "1G";
-      description = "The maximum size for uploads (e.g. images).";
-    };
-
-    poolConfig = mkOption {
-      type = with types; attrsOf (oneOf [ str int bool ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for the bookstack PHP pool. See the documentation on `php-fpm.conf`
-        for details on configuration directives.
-      '';
-    };
-
-    nginx = mkOption {
-      type = types.submodule (
-        recursiveUpdate
-          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
-      );
-      default = {};
-      example = literalExpression ''
-        {
-          serverAliases = [
-            "bookstack.''${config.networking.domain}"
-          ];
-          # To enable encryption and let let's encrypt take care of certificate
-          forceSSL = true;
-          enableACME = true;
-        }
-      '';
-      description = ''
-        With this option, you can customize the nginx virtualHost settings.
-      '';
-    };
-
-    config = mkOption {
-      type = with types;
-        attrsOf
-          (nullOr
-            (either
-              (oneOf [
-                bool
-                int
-                port
-                path
-                str
-              ])
-              (submodule {
-                options = {
-                  _secret = mkOption {
-                    type = nullOr str;
-                    description = ''
-                      The path to a file containing the value the
-                      option should be set to in the final
-                      configuration file.
-                    '';
-                  };
-                };
-              })));
-      default = {};
-      example = literalExpression ''
-        {
-          ALLOWED_IFRAME_HOSTS = "https://example.com";
-          WKHTMLTOPDF = "/home/user/bins/wkhtmltopdf";
-          AUTH_METHOD = "oidc";
-          OIDC_NAME = "MyLogin";
-          OIDC_DISPLAY_NAME_CLAIMS = "name";
-          OIDC_CLIENT_ID = "bookstack";
-          OIDC_CLIENT_SECRET = {_secret = "/run/keys/oidc_secret"};
-          OIDC_ISSUER = "https://keycloak.example.com/auth/realms/My%20Realm";
-          OIDC_ISSUER_DISCOVER = true;
-        }
-      '';
-      description = ''
-        BookStack configuration options to set in the
-        {file}`.env` file.
-
-        Refer to <https://www.bookstackapp.com/docs/>
-        for details on supported values.
-
-        Settings containing secret data should be set to an attribute
-        set containing the attribute `_secret` - a
-        string pointing to a file containing the value the option
-        should be set to. See the example to get a better picture of
-        this: in the resulting {file}`.env` file, the
-        `OIDC_CLIENT_SECRET` key will be set to the
-        contents of the {file}`/run/keys/oidc_secret`
-        file.
-      '';
-    };
-
-  };
-
-  config = mkIf cfg.enable {
-
-    assertions = [
-      { assertion = db.createLocally -> db.user == user;
-        message = "services.bookstack.database.user must be set to ${user} if services.bookstack.database.createLocally is set true.";
-      }
-      { assertion = db.createLocally -> db.passwordFile == null;
-        message = "services.bookstack.database.passwordFile cannot be specified if services.bookstack.database.createLocally is set to true.";
-      }
-    ];
-
-    services.bookstack.config = {
-      APP_KEY._secret = cfg.appKeyFile;
-      APP_URL = cfg.appURL;
-      DB_HOST = db.host;
-      DB_PORT = db.port;
-      DB_DATABASE = db.name;
-      DB_USERNAME = db.user;
-      MAIL_DRIVER = mail.driver;
-      MAIL_FROM_NAME = mail.fromName;
-      MAIL_FROM = mail.from;
-      MAIL_HOST = mail.host;
-      MAIL_PORT = mail.port;
-      MAIL_USERNAME = mail.user;
-      MAIL_ENCRYPTION = mail.encryption;
-      DB_PASSWORD._secret = db.passwordFile;
-      MAIL_PASSWORD._secret = mail.passwordFile;
-      APP_SERVICES_CACHE = "/run/bookstack/cache/services.php";
-      APP_PACKAGES_CACHE = "/run/bookstack/cache/packages.php";
-      APP_CONFIG_CACHE = "/run/bookstack/cache/config.php";
-      APP_ROUTES_CACHE = "/run/bookstack/cache/routes-v7.php";
-      APP_EVENTS_CACHE = "/run/bookstack/cache/events.php";
-      SESSION_SECURE_COOKIE = tlsEnabled;
-    };
-
-    environment.systemPackages = [ artisan ];
-
-    services.mysql = mkIf db.createLocally {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = [ db.name ];
-      ensureUsers = [
-        { name = db.user;
-          ensurePermissions = { "${db.name}.*" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    services.phpfpm.pools.bookstack = {
-      inherit user;
-      inherit group;
-      phpOptions = ''
-        log_errors = on
-        post_max_size = ${cfg.maxUploadSize}
-        upload_max_filesize = ${cfg.maxUploadSize}
-      '';
-      settings = {
-        "listen.mode" = "0660";
-        "listen.owner" = user;
-        "listen.group" = group;
-      } // cfg.poolConfig;
-    };
-
-    services.nginx = {
-      enable = mkDefault true;
-      recommendedTlsSettings = true;
-      recommendedOptimisation = true;
-      recommendedGzipSettings = true;
-      virtualHosts.${cfg.hostname} = mkMerge [ cfg.nginx {
-        root = mkForce "${bookstack}/public";
-        locations = {
-          "/" = {
-            index = "index.php";
-            tryFiles = "$uri $uri/ /index.php?$query_string";
-          };
-          "~ \.php$".extraConfig = ''
-            fastcgi_pass unix:${config.services.phpfpm.pools."bookstack".socket};
-          '';
-          "~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
-            extraConfig = "expires 365d;";
-          };
-        };
-      }];
-    };
-
-    systemd.services.bookstack-setup = {
-      description = "Preparation tasks for BookStack";
-      before = [ "phpfpm-bookstack.service" ];
-      after = optional db.createLocally "mysql.service";
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = true;
-        User = user;
-        WorkingDirectory = "${bookstack}";
-        RuntimeDirectory = "bookstack/cache";
-        RuntimeDirectoryMode = "0700";
-      };
-      path = [ pkgs.replace-secret ];
-      script =
-        let
-          isSecret = v: isAttrs v && v ? _secret && isString v._secret;
-          bookstackEnvVars = lib.generators.toKeyValue {
-            mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
-              mkValueString = v: with builtins;
-                if isInt         v then toString v
-                else if isString v then v
-                else if true  == v then "true"
-                else if false == v then "false"
-                else if isSecret v then hashString "sha256" v._secret
-                else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
-            };
-          };
-          secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
-          mkSecretReplacement = file: ''
-            replace-secret ${escapeShellArgs [ (builtins.hashString "sha256" file) file "${cfg.dataDir}/.env" ]}
-          '';
-          secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
-          filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ {} null ])) cfg.config;
-          bookstackEnv = pkgs.writeText "bookstack.env" (bookstackEnvVars filteredConfig);
-        in ''
-        # error handling
-        set -euo pipefail
-
-        # set permissions
-        umask 077
-
-        # create .env file
-        install -T -m 0600 -o ${user} ${bookstackEnv} "${cfg.dataDir}/.env"
-        ${secretReplacements}
-        if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
-            sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
-        fi
-
-        # migrate db
-        ${pkgs.php}/bin/php artisan migrate --force
-      '';
-    };
-
-    systemd.tmpfiles.settings."10-bookstack" = let
-      defaultConfig = {
-        inherit user group;
-        mode = "0700";
-      };
-    in {
-      "${cfg.dataDir}".d = defaultConfig // { mode = "0710"; };
-      "${cfg.dataDir}/public".d = defaultConfig // { mode = "0750"; };
-      "${cfg.dataDir}/public/uploads".d = defaultConfig // { mode = "0750"; };
-      "${cfg.dataDir}/storage".d = defaultConfig;
-      "${cfg.dataDir}/storage/app".d = defaultConfig;
-      "${cfg.dataDir}/storage/fonts".d = defaultConfig;
-      "${cfg.dataDir}/storage/framework".d = defaultConfig;
-      "${cfg.dataDir}/storage/framework/cache".d = defaultConfig;
-      "${cfg.dataDir}/storage/framework/sessions".d = defaultConfig;
-      "${cfg.dataDir}/storage/framework/views".d = defaultConfig;
-      "${cfg.dataDir}/storage/logs".d = defaultConfig;
-      "${cfg.dataDir}/storage/uploads".d = defaultConfig;
-    };
-
-    users = {
-      users = mkIf (user == "bookstack") {
-        bookstack = {
-          inherit group;
-          isSystemUser = true;
-        };
-        "${config.services.nginx.user}".extraGroups = [ group ];
-      };
-      groups = mkIf (group == "bookstack") {
-        bookstack = {};
-      };
-    };
-
-  };
-
-  meta.maintainers = with maintainers; [ ymarkus ];
-}
diff --git a/nixos/modules/services/web-apps/c2fmzq-server.md b/nixos/modules/services/web-apps/c2fmzq-server.md
deleted file mode 100644
index d8e59b3ad210..000000000000
--- a/nixos/modules/services/web-apps/c2fmzq-server.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# c2FmZQ {#module-services-c2fmzq}
-
-c2FmZQ is an application that can securely encrypt, store, and share files,
-including but not limited to pictures and videos.
-
-The service `c2fmzq-server` can be enabled by setting
-```nix
-{
-  services.c2fmzq-server.enable = true;
-}
-```
-This will spin up an instance of the server which is API-compatible with
-[Stingle Photos](https://stingle.org) and an experimental Progressive Web App
-(PWA) to interact with the storage via the browser.
-
-In principle the server can be exposed directly on a public interface and there
-are command line options to manage HTTPS certificates directly, but the module
-is designed to be served behind a reverse proxy or only accessed via localhost.
-
-```nix
-{
-  services.c2fmzq-server = {
-    enable = true;
-    bindIP = "127.0.0.1"; # default
-    port = 8080; # default
-  };
-
-  services.nginx = {
-    enable = true;
-    recommendedProxySettings = true;
-    virtualHosts."example.com" = {
-      enableACME = true;
-      forceSSL = true;
-      locations."/" = {
-        proxyPass = "http://127.0.0.1:8080";
-      };
-    };
-  };
-}
-```
-
-For more information, see <https://github.com/c2FmZQ/c2FmZQ/>.
diff --git a/nixos/modules/services/web-apps/c2fmzq-server.nix b/nixos/modules/services/web-apps/c2fmzq-server.nix
deleted file mode 100644
index dee131182de1..000000000000
--- a/nixos/modules/services/web-apps/c2fmzq-server.nix
+++ /dev/null
@@ -1,130 +0,0 @@
-{ lib, pkgs, config, ... }:
-
-let
-  inherit (lib) mkEnableOption mkPackageOption mkOption types;
-
-  cfg = config.services.c2fmzq-server;
-
-  argsFormat = {
-    type = with lib.types; attrsOf (nullOr (oneOf [ bool int str ]));
-    generate = lib.cli.toGNUCommandLineShell {
-      mkBool = k: v: [
-        "--${k}=${if v then "true" else "false"}"
-      ];
-    };
-  };
-in {
-  options.services.c2fmzq-server = {
-    enable = mkEnableOption "c2fmzq-server";
-
-    bindIP = mkOption {
-      type = types.str;
-      default = "127.0.0.1";
-      description = "The local address to use.";
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 8080;
-      description = "The local port to use.";
-    };
-
-    passphraseFile = mkOption {
-      type = types.str;
-      example = "/run/secrets/c2fmzq/pwfile";
-      description = "Path to file containing the database passphrase";
-    };
-
-    package = mkPackageOption pkgs "c2fmzq" { };
-
-    settings = mkOption {
-      type = types.submodule {
-        freeformType = argsFormat.type;
-
-        options = {
-          address = mkOption {
-            internal = true;
-            type = types.str;
-            default = "${cfg.bindIP}:${toString cfg.port}";
-          };
-
-          database = mkOption {
-            type = types.str;
-            default = "%S/c2fmzq-server/data";
-            description = "Path of the database";
-          };
-
-          verbose = mkOption {
-            type = types.ints.between 1 3;
-            default = 2;
-            description = "The level of logging verbosity: 1:Error 2:Info 3:Debug";
-          };
-        };
-      };
-      description = ''
-        Configuration for c2FmZQ-server passed as CLI arguments.
-        Run {command}`c2FmZQ-server help` for supported values.
-      '';
-      example = {
-        verbose = 3;
-        allow-new-accounts = true;
-        auto-approve-new-accounts = true;
-        encrypt-metadata = true;
-        enable-webapp = true;
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.c2fmzq-server = {
-      description = "c2FmZQ-server";
-      documentation = [ "https://github.com/c2FmZQ/c2FmZQ/blob/main/README.md" ];
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network.target" "network-online.target" ];
-
-      serviceConfig = {
-        ExecStart = "${lib.getExe cfg.package} ${argsFormat.generate cfg.settings}";
-        AmbientCapabilities = "";
-        CapabilityBoundingSet = "";
-        DynamicUser = true;
-        Environment = "C2FMZQ_PASSPHRASE_FILE=%d/passphrase-file";
-        IPAccounting = true;
-        IPAddressAllow = cfg.bindIP;
-        IPAddressDeny = "any";
-        LoadCredential = "passphrase-file:${cfg.passphraseFile}";
-        LockPersonality = true;
-        MemoryDenyWriteExecute = true;
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateIPC = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        ProtectSystem = "strict";
-        RemoveIPC = true;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SocketBindAllow = cfg.port;
-        SocketBindDeny = "any";
-        StateDirectory = "c2fmzq-server";
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged @obsolete" ];
-      };
-    };
-  };
-
-  meta = {
-    doc = ./c2fmzq-server.md;
-    maintainers = with lib.maintainers; [ hmenke ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/calibre-web.nix b/nixos/modules/services/web-apps/calibre-web.nix
deleted file mode 100644
index 0ca9ed2fbcf3..000000000000
--- a/nixos/modules/services/web-apps/calibre-web.nix
+++ /dev/null
@@ -1,170 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.calibre-web;
-
-  inherit (lib) concatStringsSep mkEnableOption mkIf mkOption optional optionalString types;
-in
-{
-  options = {
-    services.calibre-web = {
-      enable = mkEnableOption "Calibre-Web";
-
-      package = lib.mkPackageOption pkgs "calibre-web" { };
-
-      listen = {
-        ip = mkOption {
-          type = types.str;
-          default = "::1";
-          description = ''
-            IP address that Calibre-Web should listen on.
-          '';
-        };
-
-        port = mkOption {
-          type = types.port;
-          default = 8083;
-          description = ''
-            Listen port for Calibre-Web.
-          '';
-        };
-      };
-
-      dataDir = mkOption {
-        type = types.str;
-        default = "calibre-web";
-        description = ''
-          The directory below {file}`/var/lib` where Calibre-Web stores its data.
-        '';
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "calibre-web";
-        description = "User account under which Calibre-Web runs.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "calibre-web";
-        description = "Group account under which Calibre-Web runs.";
-      };
-
-      openFirewall = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Open ports in the firewall for the server.
-        '';
-      };
-
-      options = {
-        calibreLibrary = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = ''
-            Path to Calibre library.
-          '';
-        };
-
-        enableBookConversion = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Configure path to the Calibre's ebook-convert in the DB.
-          '';
-        };
-
-        enableKepubify = mkEnableOption "kebup conversion support";
-
-        enableBookUploading = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Allow books to be uploaded via Calibre-Web UI.
-          '';
-        };
-
-        reverseProxyAuth = {
-          enable = mkOption {
-            type = types.bool;
-            default = false;
-            description = ''
-              Enable authorization using auth proxy.
-            '';
-          };
-
-          header = mkOption {
-            type = types.str;
-            default = "";
-            description = ''
-              Auth proxy header name.
-            '';
-          };
-        };
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.calibre-web = let
-      appDb = "/var/lib/${cfg.dataDir}/app.db";
-      gdriveDb = "/var/lib/${cfg.dataDir}/gdrive.db";
-      calibreWebCmd = "${cfg.package}/bin/calibre-web -p ${appDb} -g ${gdriveDb}";
-
-      settings = concatStringsSep ", " (
-        [
-          "config_port = ${toString cfg.listen.port}"
-          "config_uploading = ${if cfg.options.enableBookUploading then "1" else "0"}"
-          "config_allow_reverse_proxy_header_login = ${if cfg.options.reverseProxyAuth.enable then "1" else "0"}"
-          "config_reverse_proxy_login_header_name = '${cfg.options.reverseProxyAuth.header}'"
-        ]
-        ++ optional (cfg.options.calibreLibrary != null) "config_calibre_dir = '${cfg.options.calibreLibrary}'"
-        ++ optional cfg.options.enableBookConversion "config_converterpath = '${pkgs.calibre}/bin/ebook-convert'"
-        ++ optional cfg.options.enableKepubify "config_kepubifypath = '${pkgs.kepubify}/bin/kepubify'"
-      );
-    in
-      {
-        description = "Web app for browsing, reading and downloading eBooks stored in a Calibre database";
-        after = [ "network.target" ];
-        wantedBy = [ "multi-user.target" ];
-
-        serviceConfig = {
-          Type = "simple";
-          User = cfg.user;
-          Group = cfg.group;
-
-          StateDirectory = cfg.dataDir;
-          ExecStartPre = pkgs.writeShellScript "calibre-web-pre-start" (
-            ''
-              __RUN_MIGRATIONS_AND_EXIT=1 ${calibreWebCmd}
-
-              ${pkgs.sqlite}/bin/sqlite3 ${appDb} "update settings set ${settings}"
-            '' + optionalString (cfg.options.calibreLibrary != null) ''
-              test -f "${cfg.options.calibreLibrary}/metadata.db" || { echo "Invalid Calibre library"; exit 1; }
-            ''
-          );
-
-          ExecStart = "${calibreWebCmd} -i ${cfg.listen.ip}";
-          Restart = "on-failure";
-        };
-      };
-
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.listen.port ];
-    };
-
-    users.users = mkIf (cfg.user == "calibre-web") {
-      calibre-web = {
-        isSystemUser = true;
-        group = cfg.group;
-      };
-    };
-
-    users.groups = mkIf (cfg.group == "calibre-web") {
-      calibre-web = {};
-    };
-  };
-
-  meta.maintainers = with lib.maintainers; [ pborzenkov ];
-}
diff --git a/nixos/modules/services/web-apps/castopod.md b/nixos/modules/services/web-apps/castopod.md
deleted file mode 100644
index 5ecd807686fd..000000000000
--- a/nixos/modules/services/web-apps/castopod.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# Castopod {#module-services-castopod}
-
-Castopod is an open-source hosting platform made for podcasters who want to engage and interact with their audience.
-
-## Quickstart {#module-services-castopod-quickstart}
-
-Configure ACME (https://nixos.org/manual/nixos/unstable/#module-security-acme).
-Use the following configuration to start a public instance of Castopod on `castopod.example.com` domain:
-
-```nix
-{
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-  services.castopod = {
-    enable = true;
-    database.createLocally = true;
-    nginx.virtualHost = {
-      serverName = "castopod.example.com";
-      enableACME = true;
-      forceSSL = true;
-    };
-  };
-}
-```
-
-Go to `https://castopod.example.com/cp-install` to create superadmin account after applying the above configuration.
diff --git a/nixos/modules/services/web-apps/castopod.nix b/nixos/modules/services/web-apps/castopod.nix
deleted file mode 100644
index d3750c3dd393..000000000000
--- a/nixos/modules/services/web-apps/castopod.nix
+++ /dev/null
@@ -1,318 +0,0 @@
-{ config, lib, pkgs, ... }:
-let
-  cfg = config.services.castopod;
-  fpm = config.services.phpfpm.pools.castopod;
-
-  user = "castopod";
-
-  # https://docs.castopod.org/getting-started/install.html#requirements
-  phpPackage = pkgs.php.withExtensions ({ enabled, all }: with all; [
-    intl
-    curl
-    mbstring
-    gd
-    exif
-    mysqlnd
-  ] ++ enabled);
-in
-{
-  meta.doc = ./castopod.md;
-  meta.maintainers = with lib.maintainers; [ alexoundos ];
-
-  options.services = {
-    castopod = {
-      enable = lib.mkEnableOption "Castopod, a hosting platform for podcasters";
-      package = lib.mkOption {
-        type = lib.types.package;
-        default = pkgs.castopod;
-        defaultText = lib.literalMD "pkgs.castopod";
-        description = "Which Castopod package to use.";
-      };
-      dataDir = lib.mkOption {
-        type = lib.types.path;
-        default = "/var/lib/castopod";
-        description = ''
-          The path where castopod stores all data. This path must be in sync
-          with the castopod package (where it is hardcoded during the build in
-          accordance with its own `dataDir` argument).
-        '';
-      };
-      database = {
-        createLocally = lib.mkOption {
-          type = lib.types.bool;
-          default = true;
-          description = ''
-            Create the database and database user locally.
-          '';
-        };
-        hostname = lib.mkOption {
-          type = lib.types.str;
-          default = "localhost";
-          description = "Database hostname.";
-        };
-        name = lib.mkOption {
-          type = lib.types.str;
-          default = "castopod";
-          description = "Database name.";
-        };
-        user = lib.mkOption {
-          type = lib.types.str;
-          default = user;
-          description = "Database user.";
-        };
-        passwordFile = lib.mkOption {
-          type = lib.types.nullOr lib.types.path;
-          default = null;
-          example = "/run/keys/castopod-dbpassword";
-          description = ''
-            A file containing the password corresponding to
-            [](#opt-services.castopod.database.user).
-
-            This file is loaded using systemd LoadCredentials.
-          '';
-        };
-      };
-      settings = lib.mkOption {
-        type = with lib.types; attrsOf (oneOf [ str int bool ]);
-        default = { };
-        example = {
-          "email.protocol" = "smtp";
-          "email.SMTPHost" = "localhost";
-          "email.SMTPUser" = "myuser";
-          "email.fromEmail" = "castopod@example.com";
-        };
-        description = ''
-          Environment variables used for Castopod.
-          See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
-          for available environment variables.
-        '';
-      };
-      environmentFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/run/keys/castopod-env";
-        description = ''
-          Environment file to inject e.g. secrets into the configuration.
-          See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
-          for available environment variables.
-
-          This file is loaded using systemd LoadCredentials.
-        '';
-      };
-      configureNginx = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = "Configure nginx as a reverse proxy for CastoPod.";
-      };
-      localDomain = lib.mkOption {
-        type = lib.types.str;
-        example = "castopod.example.org";
-        description = "The domain serving your CastoPod instance.";
-      };
-      poolSettings = lib.mkOption {
-        type = with lib.types; attrsOf (oneOf [ str int bool ]);
-        default = {
-          "pm" = "dynamic";
-          "pm.max_children" = "32";
-          "pm.start_servers" = "2";
-          "pm.min_spare_servers" = "2";
-          "pm.max_spare_servers" = "4";
-          "pm.max_requests" = "500";
-        };
-        description = ''
-          Options for Castopod's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
-        '';
-      };
-      maxUploadSize = lib.mkOption {
-        type = lib.types.str;
-        default = "512M";
-        description = ''
-          Maximum supported size for a file upload in. Maximum HTTP body
-          size is set to this value for nginx and PHP (because castopod doesn't
-          support chunked uploads yet:
-          https://code.castopod.org/adaures/castopod/-/issues/330).
-
-          Note, that practical upload size limit is smaller. For example, with
-          512 MiB setting - around 500 MiB is possible.
-        '';
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    services.castopod.settings =
-      let
-        sslEnabled = with config.services.nginx.virtualHosts.${cfg.localDomain}; addSSL || forceSSL || onlySSL || enableACME || useACMEHost != null;
-        baseURL = "http${lib.optionalString sslEnabled "s"}://${cfg.localDomain}";
-      in
-      lib.mapAttrs (_: lib.mkDefault) {
-        "app.forceGlobalSecureRequests" = sslEnabled;
-        "app.baseURL" = baseURL;
-
-        "media.baseURL" = baseURL;
-        "media.root" = "media";
-        "media.storage" = cfg.dataDir;
-
-        "admin.gateway" = "admin";
-        "auth.gateway" = "auth";
-
-        "database.default.hostname" = cfg.database.hostname;
-        "database.default.database" = cfg.database.name;
-        "database.default.username" = cfg.database.user;
-        "database.default.DBPrefix" = "cp_";
-
-        "cache.handler" = "file";
-      };
-
-    services.phpfpm.pools.castopod = {
-      inherit user;
-      group = config.services.nginx.group;
-      inherit phpPackage;
-      phpOptions = ''
-        # https://code.castopod.org/adaures/castopod/-/blob/develop/docker/production/common/uploads.template.ini
-        file_uploads = On
-        memory_limit = 512M
-        upload_max_filesize = ${cfg.maxUploadSize}
-        post_max_size = ${cfg.maxUploadSize}
-        max_execution_time = 300
-        max_input_time = 300
-      '';
-      settings = {
-        "listen.owner" = config.services.nginx.user;
-        "listen.group" = config.services.nginx.group;
-      } // cfg.poolSettings;
-    };
-
-    systemd.services.castopod-setup = {
-      after = lib.optional config.services.mysql.enable "mysql.service";
-      requires = lib.optional config.services.mysql.enable "mysql.service";
-      wantedBy = [ "multi-user.target" ];
-      path = [ pkgs.openssl phpPackage ];
-      script =
-        let
-          envFile = "${cfg.dataDir}/.env";
-          media = "${cfg.settings."media.storage"}/${cfg.settings."media.root"}";
-        in
-        ''
-          mkdir -p ${cfg.dataDir}/writable/{cache,logs,session,temp,uploads}
-
-          if [ ! -d ${lib.escapeShellArg media} ]; then
-            cp --no-preserve=mode,ownership -r ${cfg.package}/share/castopod/public/media ${lib.escapeShellArg media}
-          fi
-
-          if [ ! -f ${cfg.dataDir}/salt ]; then
-            openssl rand -base64 33 > ${cfg.dataDir}/salt
-          fi
-
-          cat <<'EOF' > ${envFile}
-          ${lib.generators.toKeyValue { } cfg.settings}
-          EOF
-
-          echo "analytics.salt=$(cat ${cfg.dataDir}/salt)" >> ${envFile}
-
-          ${if (cfg.database.passwordFile != null) then ''
-            echo "database.default.password=$(cat "$CREDENTIALS_DIRECTORY/dbpasswordfile)" >> ${envFile}
-          '' else ''
-            echo "database.default.password=" >> ${envFile}
-          ''}
-
-          ${lib.optionalString (cfg.environmentFile != null) ''
-            cat "$CREDENTIALS_DIRECTORY/envfile" >> ${envFile}
-          ''}
-
-          php ${cfg.package}/share/castopod/spark castopod:database-update
-        '';
-      serviceConfig = {
-        StateDirectory = "castopod";
-        LoadCredential = lib.optional (cfg.environmentFile != null)
-          "envfile:${cfg.environmentFile}"
-        ++ (lib.optional (cfg.database.passwordFile != null)
-          "dbpasswordfile:${cfg.database.passwordFile}");
-        WorkingDirectory = "${cfg.package}/share/castopod";
-        Type = "oneshot";
-        RemainAfterExit = true;
-        User = user;
-        Group = config.services.nginx.group;
-        ReadWritePaths = cfg.dataDir;
-      };
-    };
-
-    systemd.services.castopod-scheduled = {
-      after = [ "castopod-setup.service" ];
-      wantedBy = [ "multi-user.target" ];
-      path = [ phpPackage ];
-      script = ''
-        php ${cfg.package}/share/castopod/spark tasks:run
-      '';
-      serviceConfig = {
-        StateDirectory = "castopod";
-        WorkingDirectory = "${cfg.package}/share/castopod";
-        Type = "oneshot";
-        User = user;
-        Group = config.services.nginx.group;
-        ReadWritePaths = cfg.dataDir;
-        LogLevelMax = "notice"; # otherwise periodic tasks flood the journal
-      };
-    };
-
-    systemd.timers.castopod-scheduled = {
-      wantedBy = [ "timers.target" ];
-      timerConfig = {
-        OnCalendar = "*-*-* *:*:00";
-        Unit = "castopod-scheduled.service";
-      };
-    };
-
-    services.mysql = lib.mkIf cfg.database.createLocally {
-      enable = true;
-      package = lib.mkDefault pkgs.mariadb;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [{
-        name = cfg.database.user;
-        ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
-      }];
-    };
-
-    services.nginx = lib.mkIf cfg.configureNginx {
-      enable = true;
-      virtualHosts."${cfg.localDomain}" = {
-        root = lib.mkForce "${cfg.package}/share/castopod/public";
-
-        extraConfig = ''
-          try_files $uri $uri/ /index.php?$args;
-          index index.php index.html;
-          client_max_body_size ${cfg.maxUploadSize};
-        '';
-
-        locations."^~ /${cfg.settings."media.root"}/" = {
-          root = cfg.settings."media.storage";
-          extraConfig = ''
-            add_header Access-Control-Allow-Origin "*";
-            expires max;
-            access_log off;
-          '';
-        };
-
-        locations."~ \.php$" = {
-          fastcgiParams = {
-            SERVER_NAME = "$host";
-          };
-          extraConfig = ''
-            fastcgi_intercept_errors on;
-            fastcgi_index index.php;
-            fastcgi_pass unix:${fpm.socket};
-            try_files $uri =404;
-            fastcgi_read_timeout 3600;
-            fastcgi_send_timeout 3600;
-          '';
-        };
-      };
-    };
-
-    users.users.${user} = lib.mapAttrs (_: lib.mkDefault) {
-      description = "Castopod user";
-      isSystemUser = true;
-      group = config.services.nginx.group;
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/changedetection-io.nix b/nixos/modules/services/web-apps/changedetection-io.nix
deleted file mode 100644
index f0d72b1e4d69..000000000000
--- a/nixos/modules/services/web-apps/changedetection-io.nix
+++ /dev/null
@@ -1,220 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.changedetection-io;
-in
-{
-  options.services.changedetection-io = {
-    enable = mkEnableOption "changedetection-io";
-
-    user = mkOption {
-      default = "changedetection-io";
-      type = types.str;
-      description = ''
-        User account under which changedetection-io runs.
-      '';
-    };
-
-    group = mkOption {
-      default = "changedetection-io";
-      type = types.str;
-      description = ''
-        Group account under which changedetection-io runs.
-      '';
-    };
-
-    listenAddress = mkOption {
-      type = types.str;
-      default = "localhost";
-      description = "Address the server will listen on.";
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5000;
-      description = "Port the server will listen on.";
-    };
-
-    datastorePath = mkOption {
-      type = types.str;
-      default = "/var/lib/changedetection-io";
-      description = ''
-        The directory used to store all data for changedetection-io.
-      '';
-    };
-
-    baseURL = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "https://changedetection-io.example";
-      description = ''
-        The base url used in notifications and `{base_url}` token.
-      '';
-    };
-
-    behindProxy = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Enable this option when changedetection-io runs behind a reverse proxy, so that it trusts X-* headers.
-        It is recommend to run changedetection-io behind a TLS reverse proxy.
-      '';
-    };
-
-    environmentFile = mkOption {
-      type = types.nullOr types.path;
-      default = null;
-      example = "/run/secrets/changedetection-io.env";
-      description = ''
-        Securely pass environment variabels to changedetection-io.
-
-        This can be used to set for example a frontend password reproducible via `SALTED_PASS`
-        which convinetly also deactivates nags about the hosted version.
-        `SALTED_PASS` should be 64 characters long while the first 32 are the salt and the second the frontend password.
-        It can easily be retrieved from the settings file when first set via the frontend with the following command:
-        ``jq -r .settings.application.password /var/lib/changedetection-io/url-watches.json``
-      '';
-    };
-
-    webDriverSupport = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Enable support for fetching web pages using WebDriver and Chromium.
-        This starts a headless chromium controlled by puppeteer in an oci container.
-
-        ::: {.note}
-        Playwright can currently leak memory.
-        See https://github.com/dgtlmoon/changedetection.io/wiki/Playwright-content-fetcher#playwright-memory-leak
-        :::
-      '';
-    };
-
-    playwrightSupport = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Enable support for fetching web pages using playwright and Chromium.
-        This starts a headless Chromium controlled by puppeteer in an oci container.
-
-        ::: {.note}
-        Playwright can currently leak memory.
-        See https://github.com/dgtlmoon/changedetection.io/wiki/Playwright-content-fetcher#playwright-memory-leak
-        :::
-      '';
-    };
-
-    chromePort = mkOption {
-      type = types.port;
-      default = 4444;
-      description = ''
-        A free port on which webDriverSupport or playwrightSupport listen on localhost.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = !((cfg.webDriverSupport == true) && (cfg.playwrightSupport == true));
-        message = "'services.changedetection-io.webDriverSupport' and 'services.changedetection-io.playwrightSupport' cannot be used together.";
-      }
-    ];
-
-    systemd = let
-      defaultStateDir = cfg.datastorePath == "/var/lib/changedetection-io";
-    in {
-      services.changedetection-io = {
-        wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
-        preStart = ''
-          mkdir -p ${cfg.datastorePath}
-        '';
-        serviceConfig = {
-          User = cfg.user;
-          Group = cfg.group;
-          StateDirectory = mkIf defaultStateDir "changedetection-io";
-          StateDirectoryMode = mkIf defaultStateDir "0750";
-          WorkingDirectory = cfg.datastorePath;
-          Environment = [ "HIDE_REFERER=true" ]
-            ++ lib.optional (cfg.baseURL != null) "BASE_URL=${cfg.baseURL}"
-            ++ lib.optional cfg.behindProxy "USE_X_SETTINGS=1"
-            ++ lib.optional cfg.webDriverSupport "WEBDRIVER_URL=http://127.0.0.1:${toString cfg.chromePort}/wd/hub"
-            ++ lib.optional cfg.playwrightSupport "PLAYWRIGHT_DRIVER_URL=ws://127.0.0.1:${toString cfg.chromePort}/?stealth=1&--disable-web-security=true";
-          EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
-          ExecStart = ''
-            ${pkgs.changedetection-io}/bin/changedetection.py \
-              -h ${cfg.listenAddress} -p ${toString cfg.port} -d ${cfg.datastorePath}
-          '';
-          ProtectHome = true;
-          ProtectSystem = true;
-          Restart = "on-failure";
-        };
-      };
-      tmpfiles.rules = mkIf defaultStateDir [
-        "d ${cfg.datastorePath} 0750 ${cfg.user} ${cfg.group} - -"
-      ];
-    };
-
-    users = {
-      users = optionalAttrs (cfg.user == "changedetection-io") {
-        "changedetection-io" = {
-          isSystemUser = true;
-          group = "changedetection-io";
-        };
-      };
-
-      groups = optionalAttrs (cfg.group == "changedetection-io") {
-        "changedetection-io" = { };
-      };
-    };
-
-    virtualisation = {
-      oci-containers.containers = lib.mkMerge [
-        (mkIf cfg.webDriverSupport {
-          changedetection-io-webdriver = {
-            image = "selenium/standalone-chrome";
-            environment = {
-              VNC_NO_PASSWORD = "1";
-              SCREEN_WIDTH = "1920";
-              SCREEN_HEIGHT = "1080";
-              SCREEN_DEPTH = "24";
-            };
-            ports = [
-              "127.0.0.1:${toString cfg.chromePort}:4444"
-            ];
-            volumes = [
-              "/dev/shm:/dev/shm"
-            ];
-            extraOptions = [ "--network=bridge" ];
-          };
-        })
-
-        (mkIf cfg.playwrightSupport {
-          changedetection-io-playwright = {
-            image = "browserless/chrome";
-            environment = {
-              SCREEN_WIDTH = "1920";
-              SCREEN_HEIGHT = "1024";
-              SCREEN_DEPTH = "16";
-              ENABLE_DEBUGGER = "false";
-              PREBOOT_CHROME = "true";
-              CONNECTION_TIMEOUT = "300000";
-              MAX_CONCURRENT_SESSIONS = "10";
-              CHROME_REFRESH_TIME = "600000";
-              DEFAULT_BLOCK_ADS = "true";
-              DEFAULT_STEALTH = "true";
-            };
-            ports = [
-              "127.0.0.1:${toString cfg.chromePort}:3000"
-            ];
-            extraOptions = [ "--network=bridge" ];
-          };
-        })
-      ];
-      podman.defaultNetwork.settings.dns_enabled = true;
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/chatgpt-retrieval-plugin.nix b/nixos/modules/services/web-apps/chatgpt-retrieval-plugin.nix
deleted file mode 100644
index c1ab7ec40949..000000000000
--- a/nixos/modules/services/web-apps/chatgpt-retrieval-plugin.nix
+++ /dev/null
@@ -1,106 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.services.chatgpt-retrieval-plugin;
-in
-{
-  options.services.chatgpt-retrieval-plugin = {
-    enable = mkEnableOption "chatgpt-retrieval-plugin service";
-
-    port = mkOption {
-      type = types.port;
-      default = 8080;
-      description = "Port the chatgpt-retrieval-plugin service listens on.";
-    };
-
-    host = mkOption {
-      type = types.str;
-      default = "127.0.0.1";
-      example = "0.0.0.0";
-      description = "The hostname or IP address for chatgpt-retrieval-plugin to bind to.";
-    };
-
-    bearerTokenPath = mkOption {
-      type = types.path;
-      description = ''
-        Path to the secret bearer token used for the http api authentication.
-      '';
-      default = "";
-      example = "config.age.secrets.CHATGPT_RETRIEVAL_PLUGIN_BEARER_TOKEN.path";
-    };
-
-    openaiApiKeyPath = mkOption {
-      type = types.path;
-      description = ''
-        Path to the secret openai api key used for embeddings.
-      '';
-      default = "";
-      example = "config.age.secrets.CHATGPT_RETRIEVAL_PLUGIN_OPENAI_API_KEY.path";
-    };
-
-    datastore = mkOption {
-      type = types.enum [ "pinecone" "weaviate" "zilliz" "milvus" "qdrant" "redis" ];
-      default = "qdrant";
-      description = "This specifies the vector database provider you want to use to store and query embeddings.";
-    };
-
-    qdrantCollection = mkOption {
-      type = types.str;
-      description = ''
-        name of the qdrant collection used to store documents.
-      '';
-      default = "document_chunks";
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    assertions = [
-      {
-        assertion = cfg.bearerTokenPath != "";
-        message = "services.chatgpt-retrieval-plugin.bearerTokenPath should not be an empty string.";
-      }
-      {
-        assertion = cfg.openaiApiKeyPath != "";
-        message = "services.chatgpt-retrieval-plugin.openaiApiKeyPath should not be an empty string.";
-      }
-    ];
-
-    systemd.services.chatgpt-retrieval-plugin = {
-      description = "ChatGPT Retrieval Plugin";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        DynamicUser = true;
-        Restart = "always";
-        LoadCredential = [
-          "BEARER_TOKEN:${cfg.bearerTokenPath}"
-          "OPENAI_API_KEY:${cfg.openaiApiKeyPath}"
-        ];
-        StateDirectory = "chatgpt-retrieval-plugin";
-        StateDirectoryMode = "0755";
-      };
-
-      # it doesn't make sense to pass secrets as env vars, this is a hack until
-      # upstream has proper secret management.
-      script = ''
-        export BEARER_TOKEN=$(${pkgs.systemd}/bin/systemd-creds cat BEARER_TOKEN)
-        export OPENAI_API_KEY=$(${pkgs.systemd}/bin/systemd-creds cat OPENAI_API_KEY)
-        exec ${pkgs.chatgpt-retrieval-plugin}/bin/start --host ${cfg.host} --port ${toString cfg.port}
-      '';
-
-      environment = {
-        DATASTORE = cfg.datastore;
-        QDRANT_COLLECTION = mkIf (cfg.datastore == "qdrant") cfg.qdrantCollection;
-      };
-    };
-
-    systemd.tmpfiles.rules = [
-      # create the directory for static files for fastapi
-      "C /var/lib/chatgpt-retrieval-plugin/.well-known - - - - ${pkgs.chatgpt-retrieval-plugin}/${pkgs.python3Packages.python.sitePackages}/.well-known"
-    ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/cloudlog.nix b/nixos/modules/services/web-apps/cloudlog.nix
deleted file mode 100644
index 6550d112d537..000000000000
--- a/nixos/modules/services/web-apps/cloudlog.nix
+++ /dev/null
@@ -1,503 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.services.cloudlog;
-  dbFile = let
-    password = if cfg.database.createLocally
-               then "''"
-               else "trim(file_get_contents('${cfg.database.passwordFile}'))";
-  in pkgs.writeText "database.php" ''
-    <?php
-    defined('BASEPATH') OR exit('No direct script access allowed');
-    $active_group = 'default';
-    $query_builder = TRUE;
-    $db['default'] = array(
-      'dsn' => "",
-      'hostname' => '${cfg.database.host}',
-      'username' => '${cfg.database.user}',
-      'password' => ${password},
-      'database' => '${cfg.database.name}',
-      'dbdriver' => 'mysqli',
-      'dbprefix' => "",
-      'pconnect' => TRUE,
-      'db_debug' => (ENVIRONMENT !== 'production'),
-      'cache_on' => FALSE,
-      'cachedir' => "",
-      'char_set' => 'utf8mb4',
-      'dbcollat' => 'utf8mb4_general_ci',
-      'swap_pre' => "",
-      'encrypt' => FALSE,
-      'compress' => FALSE,
-      'stricton' => FALSE,
-      'failover' => array(),
-      'save_queries' => TRUE
-    );
-  '';
-  configFile = pkgs.writeText "config.php" ''
-    <?php
-    include('${pkgs.cloudlog}/install/config/config.php');
-    $config['datadir'] = "${cfg.dataDir}/";
-    $config['base_url'] = "${cfg.baseUrl}";
-    ${cfg.extraConfig}
-  '';
-  package = pkgs.stdenv.mkDerivation rec {
-    pname = "cloudlog";
-    version = src.version;
-    src = pkgs.cloudlog;
-    installPhase = ''
-      mkdir -p $out
-      cp -r * $out/
-
-      ln -s ${configFile} $out/application/config/config.php
-      ln -s ${dbFile} $out/application/config/database.php
-
-      # link writable directories
-      for directory in updates uploads backup logbook; do
-        rm -rf $out/$directory
-        ln -s ${cfg.dataDir}/$directory $out/$directory
-      done
-
-      # link writable asset files
-      for asset in dok sota wwff; do
-        rm -rf $out/assets/json/$asset.txt
-        ln -s ${cfg.dataDir}/assets/json/$asset.txt $out/assets/json/$asset.txt
-      done
-    '';
-  };
-in
-{
-  options.services.cloudlog = with types; {
-    enable = mkEnableOption "Cloudlog";
-    dataDir = mkOption {
-      type = str;
-      default = "/var/lib/cloudlog";
-      description = "Cloudlog data directory.";
-    };
-    baseUrl = mkOption {
-      type = str;
-      default = "http://localhost";
-      description = "Cloudlog base URL";
-    };
-    user = mkOption {
-      type = str;
-      default = "cloudlog";
-      description = "User account under which Cloudlog runs.";
-    };
-    database = {
-      createLocally = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Create the database and database user locally.";
-      };
-      host = mkOption {
-        type = str;
-        description = "MySQL database host";
-        default = "localhost";
-      };
-      name = mkOption {
-        type = str;
-        description = "MySQL database name.";
-        default = "cloudlog";
-      };
-      user = mkOption {
-        type = str;
-        description = "MySQL user name.";
-        default = "cloudlog";
-      };
-      passwordFile = mkOption {
-        type = nullOr str;
-        description = "MySQL user password file.";
-        default = null;
-      };
-    };
-    poolConfig = mkOption {
-      type = attrsOf (oneOf [ str int bool ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for Cloudlog's PHP-FPM pool.
-      '';
-    };
-    virtualHost = mkOption {
-      type = nullOr str;
-      default = "localhost";
-      description = ''
-        Name of the nginx virtualhost to use and setup. If null, do not setup
-         any virtualhost.
-      '';
-    };
-    extraConfig = mkOption {
-      description = ''
-       Any additional text to be appended to the config.php
-       configuration file. This is a PHP script. For configuration
-       settings, see <https://github.com/magicbug/Cloudlog/wiki/Cloudlog.php-Configuration-File>.
-      '';
-      default = "";
-      type = str;
-      example = ''
-        $config['show_time'] = TRUE;
-      '';
-    };
-    upload-lotw = {
-      enable = mkOption {
-        type = bool;
-        default = true;
-        description = ''
-          Whether to periodically upload logs to LoTW. If enabled, a systemd
-          timer will run the log upload task as specified by the interval
-           option.
-        '';
-      };
-      interval = mkOption {
-        type = str;
-        default = "daily";
-        description = ''
-          Specification (in the format described by systemd.time(7)) of the
-          time at which the LoTW upload will occur.
-        '';
-      };
-    };
-    upload-clublog = {
-      enable = mkOption {
-        type = bool;
-        default = true;
-        description = ''
-          Whether to periodically upload logs to Clublog. If enabled, a systemd
-          timer will run the log upload task as specified by the interval option.
-        '';
-      };
-      interval = mkOption {
-        type = str;
-        default = "daily";
-        description = ''
-          Specification (in the format described by systemd.time(7)) of the time
-          at which the Clublog upload will occur.
-        '';
-      };
-    };
-    update-lotw-users = {
-      enable = mkOption {
-        type = bool;
-        default = true;
-        description = ''
-          Whether to periodically update the list of LoTW users. If enabled, a
-          systemd timer will run the update task as specified by the interval
-          option.
-        '';
-      };
-      interval = mkOption {
-        type = str;
-        default = "weekly";
-        description = ''
-          Specification (in the format described by systemd.time(7)) of the
-          time at which the LoTW user update will occur.
-        '';
-      };
-    };
-    update-dok = {
-      enable = mkOption {
-        type = bool;
-        default = true;
-        description = ''
-          Whether to periodically update the DOK resource file. If enabled, a
-          systemd timer will run the update task as specified by the interval option.
-        '';
-      };
-      interval = mkOption {
-        type = str;
-        default = "monthly";
-        description = ''
-          Specification (in the format described by systemd.time(7)) of the
-          time at which the DOK update will occur.
-        '';
-      };
-    };
-    update-clublog-scp = {
-      enable = mkOption {
-        type = bool;
-        default = true;
-        description = ''
-          Whether to periodically update the Clublog SCP database. If enabled,
-          a systemd timer will run the update task as specified by the interval
-          option.
-        '';
-      };
-      interval = mkOption {
-        type = str;
-        default = "monthly";
-        description = ''
-          Specification (in the format described by systemd.time(7)) of the time
-          at which the Clublog SCP update will occur.
-        '';
-      };
-    };
-    update-wwff = {
-      enable = mkOption {
-        type = bool;
-        default = true;
-        description = ''
-          Whether to periodically update the WWFF database. If enabled, a
-          systemd timer will run the update task as specified by the interval
-          option.
-        '';
-      };
-      interval = mkOption {
-        type = str;
-        default = "monthly";
-        description = ''
-          Specification (in the format described by systemd.time(7)) of the time
-          at which the WWFF update will occur.
-        '';
-      };
-    };
-    upload-qrz = {
-      enable = mkOption {
-        type = bool;
-        default = true;
-        description = ''
-          Whether to periodically upload logs to QRZ. If enabled, a systemd
-          timer will run the update task as specified by the interval option.
-        '';
-      };
-      interval = mkOption {
-        type = str;
-        default = "daily";
-        description = ''
-          Specification (in the format described by systemd.time(7)) of the
-          time at which the QRZ upload will occur.
-        '';
-      };
-    };
-    update-sota = {
-      enable = mkOption {
-        type = bool;
-        default = true;
-        description = ''
-          Whether to periodically update the SOTA database. If enabled, a
-          systemd timer will run the update task as specified by the interval option.
-        '';
-      };
-      interval = mkOption {
-        type = str;
-        default = "monthly";
-        description = ''
-          Specification (in the format described by systemd.time(7)) of the time
-          at which the SOTA update will occur.
-        '';
-      };
-    };
-  };
-  config = mkIf cfg.enable {
-
-    assertions = [
-      {
-        assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
-        message = "services.cloudlog.database.passwordFile cannot be specified if services.cloudlog.database.createLocally is set to true.";
-      }
-    ];
-
-    services.phpfpm = {
-      pools.cloudlog = {
-        inherit (cfg) user;
-        group = config.services.nginx.group;
-        settings =  {
-          "listen.owner" = config.services.nginx.user;
-          "listen.group" = config.services.nginx.group;
-        } // cfg.poolConfig;
-      };
-    };
-
-    services.nginx = mkIf (cfg.virtualHost != null) {
-      enable = true;
-      virtualHosts = {
-        "${cfg.virtualHost}" = {
-          root = "${package}";
-          locations."/".tryFiles = "$uri /index.php$is_args$args";
-          locations."~ ^/index.php(/|$)".extraConfig = ''
-              include ${config.services.nginx.package}/conf/fastcgi_params;
-              include ${pkgs.nginx}/conf/fastcgi.conf;
-              fastcgi_split_path_info ^(.+\.php)(.+)$;
-              fastcgi_pass unix:${config.services.phpfpm.pools.cloudlog.socket};
-              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-            '';
-        };
-      };
-    };
-
-    services.mysql = mkIf cfg.database.createLocally {
-      enable = true;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [{
-        name = cfg.database.user;
-        ensurePermissions = {
-          "${cfg.database.name}.*" = "ALL PRIVILEGES";
-        };
-      }];
-    };
-
-    systemd = {
-      services = {
-        cloudlog-setup-database = mkIf cfg.database.createLocally {
-          description = "Set up cloudlog database";
-          serviceConfig = {
-            Type = "oneshot";
-            RemainAfterExit = true;
-          };
-          wantedBy = [ "phpfpm-cloudlog.service" ];
-          after = [ "mysql.service" ];
-          script = let
-            mysql = "${config.services.mysql.package}/bin/mysql";
-          in ''
-            if [ ! -f ${cfg.dataDir}/.dbexists ]; then
-              ${mysql} ${cfg.database.name} < ${pkgs.cloudlog}/install/assets/install.sql
-              touch ${cfg.dataDir}/.dbexists
-            fi
-        '';
-        };
-        cloudlog-upload-lotw = {
-          description = "Upload QSOs to LoTW if certs have been provided";
-          enable = cfg.upload-lotw.enable;
-          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/lotw/lotw_upload";
-        };
-        cloudlog-update-lotw-users = {
-          description = "Update LOTW Users Database";
-          enable = cfg.update-lotw-users.enable;
-          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/lotw/load_users";
-        };
-        cloudlog-update-dok = {
-          description = "Update DOK File for autocomplete";
-          enable = cfg.update-dok.enable;
-          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_dok";
-        };
-        cloudlog-update-clublog-scp = {
-          description = "Update Clublog SCP Database File";
-          enable = cfg.update-clublog-scp.enable;
-          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_clublog_scp";
-        };
-        cloudlog-update-wwff = {
-          description = "Update WWFF File for autocomplete";
-          enable = cfg.update-wwff.enable;
-          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_wwff";
-        };
-        cloudlog-upload-qrz = {
-          description = "Upload QSOs to QRZ Logbook";
-          enable = cfg.upload-qrz.enable;
-          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/qrz/upload";
-        };
-        cloudlog-update-sota = {
-          description = "Update SOTA File for autocomplete";
-          enable = cfg.update-sota.enable;
-          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_sota";
-        };
-      };
-      timers = {
-        cloudlog-upload-lotw = {
-          enable = cfg.upload-lotw.enable;
-          wantedBy = [ "timers.target" ];
-          partOf = [ "cloudlog-upload-lotw.service" ];
-          after = [ "phpfpm-cloudlog.service" ];
-          timerConfig = {
-            OnCalendar = cfg.upload-lotw.interval;
-            Persistent = true;
-          };
-        };
-        cloudlog-upload-clublog = {
-          enable = cfg.upload-clublog.enable;
-          wantedBy = [ "timers.target" ];
-          partOf = [ "cloudlog-upload-clublog.service" ];
-          after = [ "phpfpm-cloudlog.service" ];
-          timerConfig = {
-            OnCalendar = cfg.upload-clublog.interval;
-            Persistent = true;
-          };
-        };
-        cloudlog-update-lotw-users = {
-          enable = cfg.update-lotw-users.enable;
-          wantedBy = [ "timers.target" ];
-          partOf = [ "cloudlog-update-lotw-users.service" ];
-          after = [ "phpfpm-cloudlog.service" ];
-          timerConfig = {
-            OnCalendar = cfg.update-lotw-users.interval;
-            Persistent = true;
-          };
-        };
-        cloudlog-update-dok = {
-          enable = cfg.update-dok.enable;
-          wantedBy = [ "timers.target" ];
-          partOf = [ "cloudlog-update-dok.service" ];
-          after = [ "phpfpm-cloudlog.service" ];
-          timerConfig = {
-            OnCalendar = cfg.update-dok.interval;
-            Persistent = true;
-          };
-        };
-        cloudlog-update-clublog-scp = {
-          enable = cfg.update-clublog-scp.enable;
-          wantedBy = [ "timers.target" ];
-          partOf = [ "cloudlog-update-clublog-scp.service" ];
-          after = [ "phpfpm-cloudlog.service" ];
-          timerConfig = {
-            OnCalendar = cfg.update-clublog-scp.interval;
-            Persistent = true;
-          };
-        };
-        cloudlog-update-wwff =  {
-          enable = cfg.update-wwff.enable;
-          wantedBy = [ "timers.target" ];
-          partOf = [ "cloudlog-update-wwff.service" ];
-          after = [ "phpfpm-cloudlog.service" ];
-          timerConfig = {
-            OnCalendar = cfg.update-wwff.interval;
-            Persistent = true;
-          };
-        };
-        cloudlog-upload-qrz = {
-          enable = cfg.upload-qrz.enable;
-          wantedBy = [ "timers.target" ];
-          partOf = [ "cloudlog-upload-qrz.service" ];
-          after = [ "phpfpm-cloudlog.service" ];
-          timerConfig = {
-            OnCalendar = cfg.upload-qrz.interval;
-            Persistent = true;
-          };
-        };
-        cloudlog-update-sota = {
-          enable = cfg.update-sota.enable;
-          wantedBy = [ "timers.target" ];
-          partOf = [ "cloudlog-update-sota.service" ];
-          after = [ "phpfpm-cloudlog.service" ];
-          timerConfig = {
-            OnCalendar = cfg.update-sota.interval;
-            Persistent = true;
-          };
-        };
-      };
-      tmpfiles.rules = let
-        group = config.services.nginx.group;
-      in [
-        "d ${cfg.dataDir}                0750 ${cfg.user} ${group} - -"
-        "d ${cfg.dataDir}/updates        0750 ${cfg.user} ${group} - -"
-        "d ${cfg.dataDir}/uploads        0750 ${cfg.user} ${group} - -"
-        "d ${cfg.dataDir}/backup         0750 ${cfg.user} ${group} - -"
-        "d ${cfg.dataDir}/logbook        0750 ${cfg.user} ${group} - -"
-        "d ${cfg.dataDir}/assets/json    0750 ${cfg.user} ${group} - -"
-        "d ${cfg.dataDir}/assets/qslcard 0750 ${cfg.user} ${group} - -"
-      ];
-    };
-
-    users.users."${cfg.user}" = {
-      isSystemUser = true;
-      group = config.services.nginx.group;
-    };
-  };
-
-  meta.maintainers = with maintainers; [ melling ];
-}
diff --git a/nixos/modules/services/web-apps/code-server.nix b/nixos/modules/services/web-apps/code-server.nix
deleted file mode 100644
index abb5be50d353..000000000000
--- a/nixos/modules/services/web-apps/code-server.nix
+++ /dev/null
@@ -1,260 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.code-server;
-  defaultUser = "code-server";
-  defaultGroup = defaultUser;
-in {
-  options = {
-    services.code-server = {
-      enable = lib.mkEnableOption "code-server";
-
-      package = lib.mkPackageOptionMD pkgs "code-server" {
-        example = ''
-          pkgs.vscode-with-extensions.override {
-            vscode = pkgs.code-server;
-            vscodeExtensions = with pkgs.vscode-extensions; [
-              bbenoist.nix
-              dracula-theme.theme-dracula
-            ];
-          }
-        '';
-      };
-
-      extraPackages = lib.mkOption {
-        default = [ ];
-        description = ''
-          Additional packages to add to the code-server {env}`PATH`.
-        '';
-        example = lib.literalExpression "[ pkgs.go ]";
-        type = lib.types.listOf lib.types.package;
-      };
-
-      extraEnvironment = lib.mkOption {
-        type = lib.types.attrsOf lib.types.str;
-        description = ''
-          Additional environment variables to pass to code-server.
-        '';
-        default = { };
-        example = { PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig"; };
-      };
-
-      extraArguments = lib.mkOption {
-        default = [ ];
-        description = ''
-          Additional arguments to pass to code-server.
-        '';
-        example = lib.literalExpression ''[ "--log=info" ]'';
-        type = lib.types.listOf lib.types.str;
-      };
-
-      host = lib.mkOption {
-        default = "localhost";
-        description = ''
-          The host name or IP address the server should listen to.
-        '';
-        type = lib.types.str;
-      };
-
-      port = lib.mkOption {
-        default = 4444;
-        description = ''
-          The port the server should listen to.
-        '';
-        type = lib.types.port;
-      };
-
-      auth = lib.mkOption {
-        default = "password";
-        description = ''
-          The type of authentication to use.
-        '';
-        type = lib.types.enum [ "none" "password" ];
-      };
-
-      hashedPassword = lib.mkOption {
-        default = "";
-        description = ''
-          Create the password with: `echo -n 'thisismypassword' | npx argon2-cli -e`.
-        '';
-        type = lib.types.str;
-      };
-
-      user = lib.mkOption {
-        default = defaultUser;
-        example = "yourUser";
-        description = ''
-          The user to run code-server as.
-          By default, a user named `${defaultUser}` will be created.
-        '';
-        type = lib.types.str;
-      };
-
-      group = lib.mkOption {
-        default = defaultGroup;
-        example = "yourGroup";
-        description = ''
-          The group to run code-server under.
-          By default, a group named `${defaultGroup}` will be created.
-        '';
-        type = lib.types.str;
-      };
-
-      extraGroups = lib.mkOption {
-        default = [ ];
-        description = ''
-          An array of additional groups for the `${defaultUser}` user.
-        '';
-        example = [ "docker" ];
-        type = lib.types.listOf lib.types.str;
-      };
-
-      socket = lib.mkOption {
-        default = null;
-        example = "/run/code-server/socket";
-        description = ''
-          Path to a socket (bind-addr will be ignored).
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      socketMode = lib.mkOption {
-        default = null;
-        description = ''
-           File mode of the socket.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      userDataDir = lib.mkOption {
-        default = null;
-        description = ''
-          Path to the user data directory.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      extensionsDir = lib.mkOption {
-        default = null;
-        description = ''
-          Path to the extensions directory.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      proxyDomain = lib.mkOption {
-        default = null;
-        example = "code-server.lan";
-        description = ''
-          Domain used for proxying ports.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      disableTelemetry = lib.mkOption {
-        default = false;
-        example = true;
-        description = ''
-          Disable telemetry.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableUpdateCheck = lib.mkOption {
-        default = false;
-        example = true;
-        description = ''
-          Disable update check.
-          Without this flag, code-server checks every 6 hours against the latest github release and
-          then notifies you once every week that a new release is available.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableFileDownloads = lib.mkOption {
-        default = false;
-        example = true;
-        description = ''
-          Disable file downloads from Code.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableWorkspaceTrust = lib.mkOption {
-        default = false;
-        example = true;
-        description = ''
-          Disable Workspace Trust feature.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableGettingStartedOverride = lib.mkOption {
-        default = false;
-        example = true;
-        description = ''
-          Disable the coder/coder override in the Help: Getting Started page.
-        '';
-        type = lib.types.bool;
-      };
-
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.code-server = {
-      description = "Code server";
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network-online.target" ];
-      path = cfg.extraPackages;
-      environment = {
-        HASHED_PASSWORD = cfg.hashedPassword;
-      } // cfg.extraEnvironment;
-      serviceConfig = {
-        ExecStart = ''
-          ${lib.getExe cfg.package} \
-            --auth=${cfg.auth} \
-            --bind-addr=${cfg.host}:${toString cfg.port} \
-          '' + lib.optionalString (cfg.socket != null) ''
-            --socket=${cfg.socket} \
-          '' + lib.optionalString (cfg.userDataDir != null) ''
-            --user-data-dir=${cfg.userDataDir} \
-          '' + lib.optionalString (cfg.extensionsDir != null) ''
-            --extensions-dir=${cfg.extensionsDir} \
-          '' + lib.optionalString (cfg.disableTelemetry == true) ''
-            --disable-telemetry \
-          '' + lib.optionalString (cfg.disableUpdateCheck == true) ''
-            --disable-update-check \
-          '' + lib.optionalString (cfg.disableFileDownloads == true) ''
-            --disable-file-downloads \
-          '' + lib.optionalString (cfg.disableWorkspaceTrust == true) ''
-            --disable-workspace-trust \
-          '' + lib.optionalString (cfg.disableGettingStartedOverride == true) ''
-            --disable-getting-started-override \
-          '' + lib.escapeShellArgs cfg.extraArguments;
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        RuntimeDirectory = cfg.user;
-        User = cfg.user;
-        Group = cfg.group;
-        Restart = "on-failure";
-      };
-    };
-
-    users.users."${cfg.user}" = lib.mkMerge [
-      (lib.mkIf (cfg.user == defaultUser) {
-        isNormalUser = true;
-        description = "code-server user";
-        inherit (cfg) group;
-      })
-      {
-        packages = cfg.extraPackages;
-        inherit (cfg) extraGroups;
-      }
-    ];
-
-    users.groups."${defaultGroup}" = lib.mkIf (cfg.group == defaultGroup) { };
-  };
-
-  meta.maintainers = [ lib.maintainers.stackshadow ];
-}
diff --git a/nixos/modules/services/web-apps/coder.nix b/nixos/modules/services/web-apps/coder.nix
deleted file mode 100644
index 5450adbe118d..000000000000
--- a/nixos/modules/services/web-apps/coder.nix
+++ /dev/null
@@ -1,227 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.coder;
-  name = "coder";
-in {
-  options = {
-    services.coder = {
-      enable = mkEnableOption "Coder service";
-
-      user = mkOption {
-        type = types.str;
-        default = "coder";
-        description = ''
-          User under which the coder service runs.
-
-          ::: {.note}
-          If left as the default value this user will automatically be created
-          on system activation, otherwise it needs to be configured manually.
-          :::
-        '';
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "coder";
-        description = ''
-          Group under which the coder service runs.
-
-          ::: {.note}
-          If left as the default value this group will automatically be created
-          on system activation, otherwise it needs to be configured manually.
-          :::
-        '';
-      };
-
-      package = mkPackageOption pkgs "coder" { };
-
-      homeDir = mkOption {
-        type = types.str;
-        description = ''
-          Home directory for coder user.
-        '';
-        default = "/var/lib/coder";
-      };
-
-      listenAddress = mkOption {
-        type = types.str;
-        description = ''
-          Listen address.
-        '';
-        default = "127.0.0.1:3000";
-      };
-
-      accessUrl = mkOption {
-        type = types.nullOr types.str;
-        description = ''
-          Access URL should be a external IP address or domain with DNS records pointing to Coder.
-        '';
-        default = null;
-        example = "https://coder.example.com";
-      };
-
-      wildcardAccessUrl = mkOption {
-        type = types.nullOr types.str;
-        description = ''
-          If you are providing TLS certificates directly to the Coder server, you must use a single certificate for the root and wildcard domains.
-        '';
-        default = null;
-        example = "*.coder.example.com";
-      };
-
-      environment = {
-        extra = mkOption {
-          type = types.attrs;
-          description = "Extra environment variables to pass run Coder's server with. See Coder documentation.";
-          default = {};
-          example = {
-            CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS = true;
-            CODER_OAUTH2_GITHUB_ALLOWED_ORGS = "your-org";
-          };
-        };
-        file = mkOption {
-          type = types.nullOr types.path;
-          description = "Systemd environment file to add to Coder.";
-          default = null;
-        };
-      };
-
-      database = {
-        createLocally = mkOption {
-          type = types.bool;
-          default = true;
-          description = ''
-            Create the database and database user locally.
-          '';
-        };
-
-        host = mkOption {
-          type = types.str;
-          default = "/run/postgresql";
-          description = ''
-            Hostname hosting the database.
-          '';
-        };
-
-        database = mkOption {
-          type = types.str;
-          default = "coder";
-          description = ''
-            Name of database.
-          '';
-        };
-
-        username = mkOption {
-          type = types.str;
-          default = "coder";
-          description = ''
-            Username for accessing the database.
-          '';
-        };
-
-        password = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          description = ''
-            Password for accessing the database.
-          '';
-        };
-
-        sslmode = mkOption {
-          type = types.nullOr types.str;
-          default = "disable";
-          description = ''
-            Password for accessing the database.
-          '';
-        };
-      };
-
-      tlsCert = mkOption {
-        type = types.nullOr types.path;
-        description = ''
-          The path to the TLS certificate.
-        '';
-        default = null;
-      };
-
-      tlsKey = mkOption {
-        type = types.nullOr types.path;
-        description = ''
-          The path to the TLS key.
-        '';
-        default = null;
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.username == name && cfg.database.database == cfg.database.username;
-        message = "services.coder.database.username must be set to ${name} if services.coder.database.createLocally is set true";
-      }
-    ];
-
-    systemd.services.coder = {
-      description = "Coder - Self-hosted developer workspaces on your infra";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      environment = cfg.environment.extra // {
-        CODER_ACCESS_URL = cfg.accessUrl;
-        CODER_WILDCARD_ACCESS_URL = cfg.wildcardAccessUrl;
-        CODER_PG_CONNECTION_URL = "user=${cfg.database.username} ${optionalString (cfg.database.password != null) "password=${cfg.database.password}"} database=${cfg.database.database} host=${cfg.database.host} ${optionalString (cfg.database.sslmode != null) "sslmode=${cfg.database.sslmode}"}";
-        CODER_ADDRESS = cfg.listenAddress;
-        CODER_TLS_ENABLE = optionalString (cfg.tlsCert != null) "1";
-        CODER_TLS_CERT_FILE = cfg.tlsCert;
-        CODER_TLS_KEY_FILE = cfg.tlsKey;
-      };
-
-      serviceConfig = {
-        ProtectSystem = "full";
-        PrivateTmp = "yes";
-        PrivateDevices = "yes";
-        SecureBits = "keep-caps";
-        AmbientCapabilities = "CAP_IPC_LOCK CAP_NET_BIND_SERVICE";
-        CacheDirectory = "coder";
-        CapabilityBoundingSet = "CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE";
-        KillSignal = "SIGINT";
-        KillMode = "mixed";
-        NoNewPrivileges = "yes";
-        Restart = "on-failure";
-        ExecStart = "${cfg.package}/bin/coder server";
-        User = cfg.user;
-        Group = cfg.group;
-        EnvironmentFile = lib.mkIf (cfg.environment.file != null) cfg.environment.file;
-      };
-    };
-
-    services.postgresql = lib.mkIf cfg.database.createLocally {
-      enable = true;
-      ensureDatabases = [
-        cfg.database.database
-      ];
-      ensureUsers = [{
-        name = cfg.user;
-        ensureDBOwnership = true;
-        }
-      ];
-    };
-
-    users.groups = optionalAttrs (cfg.group == name) {
-      "${cfg.group}" = {};
-    };
-    users.users = optionalAttrs (cfg.user == name) {
-      ${name} = {
-        description = "Coder service user";
-        group = cfg.group;
-        home = cfg.homeDir;
-        createHome = true;
-        isSystemUser = true;
-      };
-    };
-  };
-  meta.maintainers = pkgs.coder.meta.maintainers;
-}
diff --git a/nixos/modules/services/web-apps/commafeed.nix b/nixos/modules/services/web-apps/commafeed.nix
deleted file mode 100644
index 354e3625bb99..000000000000
--- a/nixos/modules/services/web-apps/commafeed.nix
+++ /dev/null
@@ -1,114 +0,0 @@
-{
-  config,
-  lib,
-  pkgs,
-  ...
-}:
-let
-  cfg = config.services.commafeed;
-in
-{
-  options.services.commafeed = {
-    enable = lib.mkEnableOption "CommaFeed";
-
-    package = lib.mkPackageOption pkgs "commafeed" { };
-
-    user = lib.mkOption {
-      type = lib.types.str;
-      description = "User under which CommaFeed runs.";
-      default = "commafeed";
-    };
-
-    group = lib.mkOption {
-      type = lib.types.str;
-      description = "Group under which CommaFeed runs.";
-      default = "commafeed";
-    };
-
-    stateDir = lib.mkOption {
-      type = lib.types.path;
-      description = "Directory holding all state for CommaFeed to run.";
-      default = "/var/lib/commafeed";
-    };
-
-    environment = lib.mkOption {
-      type = lib.types.attrsOf (
-        lib.types.oneOf [
-          lib.types.bool
-          lib.types.int
-          lib.types.str
-        ]
-      );
-      description = ''
-        Extra environment variables passed to CommaFeed, refer to
-        <https://github.com/Athou/commafeed/blob/master/commafeed-server/config.yml.example>
-        for supported values. The default user is `admin` and the default password is `admin`.
-        Correct configuration for H2 database is already provided.
-      '';
-      default = { };
-      example = {
-        CF_SERVER_APPLICATIONCONNECTORS_0_TYPE = "http";
-        CF_SERVER_APPLICATIONCONNECTORS_0_PORT = 9090;
-      };
-    };
-
-    environmentFile = lib.mkOption {
-      type = lib.types.nullOr lib.types.path;
-      description = ''
-        Environment file as defined in {manpage}`systemd.exec(5)`.
-      '';
-      default = null;
-      example = "/var/lib/commafeed/commafeed.env";
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.commafeed = {
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      environment = lib.mapAttrs (
-        _: v: if lib.isBool v then lib.boolToString v else toString v
-      ) cfg.environment;
-      serviceConfig = {
-        ExecStart = "${lib.getExe cfg.package} server ${cfg.package}/share/config.yml";
-        User = cfg.user;
-        Group = cfg.group;
-        StateDirectory = baseNameOf cfg.stateDir;
-        WorkingDirectory = cfg.stateDir;
-        # Hardening
-        CapabilityBoundingSet = [ "" ];
-        DevicePolicy = "closed";
-        DynamicUser = true;
-        LockPersonality = true;
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateUsers = true;
-        ProcSubset = "pid";
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        ProtectSystem = true;
-        RestrictAddressFamilies = [
-          "AF_INET"
-          "AF_INET6"
-        ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [
-          "@system-service"
-          "~@privileged"
-        ];
-        UMask = "0077";
-      } // lib.optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = cfg.environmentFile; };
-    };
-  };
-
-  meta.maintainers = [ lib.maintainers.raroh73 ];
-}
diff --git a/nixos/modules/services/web-apps/convos.nix b/nixos/modules/services/web-apps/convos.nix
deleted file mode 100644
index da5f7cbf724f..000000000000
--- a/nixos/modules/services/web-apps/convos.nix
+++ /dev/null
@@ -1,72 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.convos;
-in
-{
-  options.services.convos = {
-    enable = mkEnableOption "Convos";
-    listenPort = mkOption {
-      type = types.port;
-      default = 3000;
-      example = 8080;
-      description = "Port the web interface should listen on";
-    };
-    listenAddress = mkOption {
-      type = types.str;
-      default = "*";
-      example = "127.0.0.1";
-      description = "Address or host the web interface should listen on";
-    };
-    reverseProxy = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Enables reverse proxy support. This will allow Convos to automatically
-        pick up the `X-Forwarded-For` and
-        `X-Request-Base` HTTP headers set in your reverse proxy
-        web server. Note that enabling this option without a reverse proxy in
-        front will be a security issue.
-      '';
-    };
-  };
-  config = mkIf cfg.enable {
-    systemd.services.convos = {
-      description = "Convos Service";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "networking.target" ];
-      environment = {
-        CONVOS_HOME = "%S/convos";
-        CONVOS_REVERSE_PROXY = if cfg.reverseProxy then "1" else "0";
-        MOJO_LISTEN = "http://${toString cfg.listenAddress}:${toString cfg.listenPort}";
-      };
-      serviceConfig = {
-        ExecStart = "${pkgs.convos}/bin/convos daemon";
-        Restart = "on-failure";
-        StateDirectory = "convos";
-        WorkingDirectory = "%S/convos";
-        DynamicUser = true;
-        MemoryDenyWriteExecute = true;
-        ProtectHome = true;
-        ProtectClock = true;
-        ProtectHostname = true;
-        ProtectKernelTunables = true;
-        ProtectKernelModules = true;
-        ProtectKernelLogs = true;
-        ProtectControlGroups = true;
-        PrivateDevices = true;
-        PrivateMounts = true;
-        PrivateUsers = true;
-        LockPersonality = true;
-        RestrictRealtime = true;
-        RestrictNamespaces = true;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6"];
-        SystemCallFilter = "@system-service";
-        SystemCallArchitectures = "native";
-        CapabilityBoundingSet = "";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/crabfit.nix b/nixos/modules/services/web-apps/crabfit.nix
deleted file mode 100644
index d58027a6965d..000000000000
--- a/nixos/modules/services/web-apps/crabfit.nix
+++ /dev/null
@@ -1,171 +0,0 @@
-{
-  config,
-  lib,
-  pkgs,
-  ...
-}:
-
-let
-  inherit (lib)
-    literalExpression
-    mkEnableOption
-    mkIf
-    mkOption
-    mkPackageOption
-    ;
-
-  inherit (lib.types)
-    attrsOf
-    package
-    port
-    str
-    ;
-
-  cfg = config.services.crabfit;
-in
-
-{
-  options.services.crabfit = {
-    enable = mkEnableOption "Crab Fit, a meeting scheduler based on peoples' availability";
-
-    frontend = {
-      package = mkPackageOption pkgs "crabfit-frontend" { };
-
-      finalDrv = mkOption {
-        readOnly = true;
-        type = package;
-        default = cfg.frontend.package.override {
-          api_url = "https://${cfg.api.host}";
-          frontend_url = cfg.frontend.host;
-        };
-
-        defaultText = literalExpression ''
-          cfg.package.override {
-            api_url = "https://''${cfg.api.host}";
-            frontend_url = cfg.frontend.host;
-          };
-        '';
-
-        description = ''
-          The patched frontend, using the correct urls for the API and frontend.
-        '';
-      };
-
-      environment = mkOption {
-        type = attrsOf str;
-        default = { };
-        description = ''
-          Environment variables for the crabfit frontend.
-        '';
-      };
-
-      host = mkOption {
-        type = str;
-        description = ''
-          The hostname of the frontend.
-        '';
-      };
-
-      port = mkOption {
-        type = port;
-        default = 3001;
-        description = ''
-          The internal listening port of the frontend.
-        '';
-      };
-    };
-
-    api = {
-      package = mkPackageOption pkgs "crabfit-api" { };
-
-      environment = mkOption {
-        type = attrsOf str;
-        default = { };
-        description = ''
-          Environment variables for the crabfit API.
-        '';
-      };
-
-      host = mkOption {
-        type = str;
-        description = ''
-          The hostname of the API.
-        '';
-      };
-
-      port = mkOption {
-        type = port;
-        default = 3000;
-        description = ''
-          The internal listening port of the API.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services = {
-      crabfit-api = {
-        description = "The API for Crab Fit.";
-
-        wantedBy = [ "multi-user.target" ];
-        after = [ "postgresql.service" ];
-
-        serviceConfig = {
-          # TODO: harden
-          ExecStart = lib.getExe cfg.api.package;
-          User = "crabfit";
-        };
-
-        environment = {
-          API_LISTEN = "127.0.0.1:${builtins.toString cfg.api.port}";
-          DATABASE_URL = "postgres:///crabfit?host=/run/postgresql";
-          FRONTEND_URL = "https://${cfg.frontend.host}";
-        } // cfg.api.environment;
-      };
-
-      crabfit-frontend = {
-        description = "The frontend for Crab Fit.";
-
-        wantedBy = [ "multi-user.target" ];
-
-        serviceConfig = {
-          # TODO: harden
-          CacheDirectory = "crabfit";
-          DynamicUser = true;
-          ExecStart = "${lib.getExe pkgs.nodejs} standalone/server.js";
-          WorkingDirectory = cfg.frontend.finalDrv;
-        };
-
-        environment = {
-          NEXT_PUBLIC_API_URL = "https://${cfg.api.host}";
-          PORT = builtins.toString cfg.frontend.port;
-        } // cfg.frontend.environment;
-      };
-    };
-
-    users = {
-      groups.crabfit = { };
-
-      users.crabfit = {
-        group = "crabfit";
-        isSystemUser = true;
-      };
-    };
-
-    services = {
-      postgresql = {
-        enable = true;
-
-        ensureDatabases = [ "crabfit" ];
-
-        ensureUsers = [
-          {
-            name = "crabfit";
-            ensureDBOwnership = true;
-          }
-        ];
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/davis.md b/nixos/modules/services/web-apps/davis.md
deleted file mode 100644
index 9775d8221b5b..000000000000
--- a/nixos/modules/services/web-apps/davis.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# Davis {#module-services-davis}
-
-[Davis](https://github.com/tchapi/davis/) is a caldav and carrddav server. It
-has a simple, fully translatable admin interface for sabre/dav based on Symfony
-5 and Bootstrap 5, initially inspired by Baïkal.
-
-## Basic Usage {#module-services-davis-basic-usage}
-
-At first, an application secret is needed, this can be generated with:
-```ShellSession
-$ cat /dev/urandom | tr -dc a-zA-Z0-9 | fold -w 48 | head -n 1
-```
-
-After that, `davis` can be deployed like this:
-```
-{
-  services.davis = {
-    enable = true;
-    hostname = "davis.example.com";
-    mail = {
-      dsn = "smtp://username@example.com:25";
-      inviteFromAddress = "davis@example.com";
-    };
-    adminLogin = "admin";
-    adminPasswordFile = "/run/secrets/davis-admin-password";
-    appSecretFile = "/run/secrets/davis-app-secret";
-    nginx = {};
-  };
-}
-```
-
-This deploys Davis using a sqlite database running out of `/var/lib/davis`.
diff --git a/nixos/modules/services/web-apps/davis.nix b/nixos/modules/services/web-apps/davis.nix
deleted file mode 100644
index d9b28020dc2d..000000000000
--- a/nixos/modules/services/web-apps/davis.nix
+++ /dev/null
@@ -1,554 +0,0 @@
-{
-  config,
-  lib,
-  pkgs,
-  ...
-}:
-
-let
-  cfg = config.services.davis;
-  db = cfg.database;
-  mail = cfg.mail;
-
-  mysqlLocal = db.createLocally && db.driver == "mysql";
-  pgsqlLocal = db.createLocally && db.driver == "postgresql";
-
-  user = cfg.user;
-  group = cfg.group;
-
-  isSecret = v: lib.isAttrs v && v ? _secret && (lib.isString v._secret || builtins.isPath v._secret);
-  davisEnvVars = lib.generators.toKeyValue {
-    mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
-      mkValueString =
-        v:
-        if builtins.isInt v then
-          toString v
-        else if lib.isString v then
-          "\"${v}\""
-        else if true == v then
-          "true"
-        else if false == v then
-          "false"
-        else if null == v then
-          ""
-        else if isSecret v then
-          if (lib.isString v._secret) then
-            builtins.hashString "sha256" v._secret
-          else
-            builtins.hashString "sha256" (builtins.readFile v._secret)
-        else
-          throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty { }) v}";
-    };
-  };
-  secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
-  mkSecretReplacement = file: ''
-    replace-secret ${
-      lib.escapeShellArgs [
-        (
-          if (lib.isString file) then
-            builtins.hashString "sha256" file
-          else
-            builtins.hashString "sha256" (builtins.readFile file)
-        )
-        file
-        "${cfg.dataDir}/.env.local"
-      ]
-    }
-  '';
-  secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
-  filteredConfig = lib.converge (lib.filterAttrsRecursive (
-    _: v:
-    !lib.elem v [
-      { }
-      null
-    ]
-  )) cfg.config;
-  davisEnv = pkgs.writeText "davis.env" (davisEnvVars filteredConfig);
-in
-{
-  options.services.davis = {
-    enable = lib.mkEnableOption "Davis is a caldav and carddav server";
-
-    user = lib.mkOption {
-      default = "davis";
-      description = "User davis runs as.";
-      type = lib.types.str;
-    };
-
-    group = lib.mkOption {
-      default = "davis";
-      description = "Group davis runs as.";
-      type = lib.types.str;
-    };
-
-    package = lib.mkPackageOption pkgs "davis" { };
-
-    dataDir = lib.mkOption {
-      type = lib.types.path;
-      default = "/var/lib/davis";
-      description = ''
-        Davis data directory.
-      '';
-    };
-
-    hostname = lib.mkOption {
-      type = lib.types.str;
-      example = "davis.yourdomain.org";
-      description = ''
-        Domain of the host to serve davis under. You may want to change it if you
-        run Davis on a different URL than davis.yourdomain.
-      '';
-    };
-
-    config = lib.mkOption {
-      type = lib.types.attrsOf (
-        lib.types.nullOr (
-          lib.types.either
-            (lib.types.oneOf [
-              lib.types.bool
-              lib.types.int
-              lib.types.port
-              lib.types.path
-              lib.types.str
-            ])
-            (
-              lib.types.submodule {
-                options = {
-                  _secret = lib.mkOption {
-                    type = lib.types.nullOr (
-                      lib.types.oneOf [
-                        lib.types.str
-                        lib.types.path
-                      ]
-                    );
-                    description = ''
-                      The path to a file containing the value the
-                      option should be set to in the final
-                      configuration file.
-                    '';
-                  };
-                };
-              }
-            )
-        )
-      );
-      default = { };
-
-      example = '''';
-      description = '''';
-    };
-
-    adminLogin = lib.mkOption {
-      type = lib.types.str;
-      default = "root";
-      description = ''
-        Username for the admin account.
-      '';
-    };
-    adminPasswordFile = lib.mkOption {
-      type = lib.types.path;
-      description = ''
-        The full path to a file that contains the admin's password. Must be
-        readable by the user.
-      '';
-      example = "/run/secrets/davis-admin-pass";
-    };
-
-    appSecretFile = lib.mkOption {
-      type = lib.types.path;
-      description = ''
-        A file containing the Symfony APP_SECRET - Its value should be a series
-        of characters, numbers and symbols chosen randomly and the recommended
-        length is around 32 characters. Can be generated with <code>cat
-        /dev/urandom | tr -dc a-zA-Z0-9 | fold -w 48 | head -n 1</code>.
-      '';
-      example = "/run/secrets/davis-appsecret";
-    };
-
-    database = {
-      driver = lib.mkOption {
-        type = lib.types.enum [
-          "sqlite"
-          "postgresql"
-          "mysql"
-        ];
-        default = "sqlite";
-        description = "Database type, required in all circumstances.";
-      };
-      urlFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/run/secrets/davis-db-url";
-        description = ''
-          A file containing the database connection url. If set then it
-          overrides all other database settings (except driver). This is
-          mandatory if you want to use an external database, that is when
-          `services.davis.database.createLocally` is `false`.
-        '';
-      };
-      name = lib.mkOption {
-        type = lib.types.nullOr lib.types.str;
-        default = "davis";
-        description = "Database name, only used when the databse is created locally.";
-      };
-      createLocally = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = "Create the database and database user locally.";
-      };
-    };
-
-    mail = {
-      dsn = lib.mkOption {
-        type = lib.types.nullOr lib.types.str;
-        default = null;
-        description = "Mail DSN for sending emails. Mutually exclusive with `services.davis.mail.dsnFile`.";
-        example = "smtp://username:password@example.com:25";
-      };
-      dsnFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.str;
-        default = null;
-        example = "/run/secrets/davis-mail-dsn";
-        description = "A file containing the mail DSN for sending emails.  Mutually exclusive with `servies.davis.mail.dsn`.";
-      };
-      inviteFromAddress = lib.mkOption {
-        type = lib.types.nullOr lib.types.str;
-        default = null;
-        description = "Email address to send invitations from.";
-        example = "no-reply@dav.example.com";
-      };
-    };
-
-    nginx = lib.mkOption {
-      type = lib.types.submodule (
-        lib.recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) { }
-      );
-      default = null;
-      example = ''
-        {
-          serverAliases = [
-            "dav.''${config.networking.domain}"
-          ];
-          # To enable encryption and let let's encrypt take care of certificate
-          forceSSL = true;
-          enableACME = true;
-        }
-      '';
-      description = ''
-        With this option, you can customize the nginx virtualHost settings.
-      '';
-    };
-
-    poolConfig = lib.mkOption {
-      type = lib.types.attrsOf (
-        lib.types.oneOf [
-          lib.types.str
-          lib.types.int
-          lib.types.bool
-        ]
-      );
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for the davis PHP pool. See the documentation on <literal>php-fpm.conf</literal>
-        for details on configuration directives.
-      '';
-    };
-  };
-
-  config =
-    let
-      defaultServiceConfig = {
-        ReadWritePaths = "${cfg.dataDir}";
-        User = user;
-        UMask = 77;
-        DeviceAllow = "";
-        LockPersonality = true;
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        ProcSubset = "pid";
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        ProtectSystem = "strict";
-        RemoveIPC = true;
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [
-          "@system-service"
-          "~@resources"
-          "~@privileged"
-        ];
-        WorkingDirectory = "${cfg.package}/";
-      };
-    in
-    lib.mkIf cfg.enable {
-      assertions = [
-        {
-          assertion = db.createLocally -> db.urlFile == null;
-          message = "services.davis.database.urlFile must be unset if services.davis.database.createLocally is set true.";
-        }
-        {
-          assertion = db.createLocally || db.urlFile != null;
-          message = "One of services.davis.database.urlFile or services.davis.database.createLocally must be set.";
-        }
-        {
-          assertion = (mail.dsn != null) != (mail.dsnFile != null);
-          message = "One of (and only one of) services.davis.mail.dsn or services.davis.mail.dsnFile must be set.";
-        }
-      ];
-      services.davis.config =
-        {
-          APP_ENV = "prod";
-          APP_CACHE_DIR = "${cfg.dataDir}/var/cache";
-          # note: we do not need the log dir (we log to stdout/journald), by davis/symfony will try to create it, and the default value is one in the nix-store
-          #       so we set it to a path under dataDir to avoid something like: Unable to create the "logs" directory (/nix/store/5cfskz0ybbx37s1161gjn5klwb5si1zg-davis-4.4.1/var/log).
-          APP_LOG_DIR = "${cfg.dataDir}/var/log";
-          LOG_FILE_PATH = "/dev/stdout";
-          DATABASE_DRIVER = db.driver;
-          INVITE_FROM_ADDRESS = mail.inviteFromAddress;
-          APP_SECRET._secret = cfg.appSecretFile;
-          ADMIN_LOGIN = cfg.adminLogin;
-          ADMIN_PASSWORD._secret = cfg.adminPasswordFile;
-          APP_TIMEZONE = config.time.timeZone;
-          WEBDAV_ENABLED = false;
-          CALDAV_ENABLED = true;
-          CARDDAV_ENABLED = true;
-        }
-        // (if mail.dsn != null then { MAILER_DSN = mail.dsn; } else { MAILER_DSN._secret = mail.dsnFile; })
-        // (
-          if db.createLocally then
-            {
-              DATABASE_URL =
-                if db.driver == "sqlite" then
-                  "sqlite:///${cfg.dataDir}/davis.db" # note: sqlite needs 4 slashes for an absolute path
-                else if
-                  pgsqlLocal
-                # note: davis expects a non-standard postgres uri (due to the underlying doctrine library)
-                # specifically the dummy hostname which is overriden by the host query parameter
-                then
-                  "postgres://${user}@localhost/${db.name}?host=/run/postgresql"
-                else if mysqlLocal then
-                  "mysql://${user}@localhost/${db.name}?socket=/run/mysqld/mysqld.sock"
-                else
-                  null;
-            }
-          else
-            { DATABASE_URL._secret = db.urlFile; }
-        );
-
-      users = {
-        users = lib.mkIf (user == "davis") {
-          davis = {
-            description = "Davis service user";
-            group = cfg.group;
-            isSystemUser = true;
-            home = cfg.dataDir;
-          };
-        };
-        groups = lib.mkIf (group == "davis") { davis = { }; };
-      };
-
-      systemd.tmpfiles.rules = [
-        "d ${cfg.dataDir}                            0710 ${user} ${group} - -"
-        "d ${cfg.dataDir}/var                        0700 ${user} ${group} - -"
-        "d ${cfg.dataDir}/var/log                    0700 ${user} ${group} - -"
-        "d ${cfg.dataDir}/var/cache                  0700 ${user} ${group} - -"
-      ];
-
-      services.phpfpm.pools.davis = {
-        inherit user group;
-        phpOptions = ''
-          log_errors = on
-        '';
-        phpEnv = {
-          ENV_DIR = "${cfg.dataDir}";
-          APP_CACHE_DIR = "${cfg.dataDir}/var/cache";
-          APP_LOG_DIR = "${cfg.dataDir}/var/log";
-        };
-        settings =
-          {
-            "listen.mode" = "0660";
-            "pm" = "dynamic";
-            "pm.max_children" = 256;
-            "pm.start_servers" = 10;
-            "pm.min_spare_servers" = 5;
-            "pm.max_spare_servers" = 20;
-          }
-          // (
-            if cfg.nginx != null then
-              {
-                "listen.owner" = config.services.nginx.user;
-                "listen.group" = config.services.nginx.group;
-              }
-            else
-              { }
-          )
-          // cfg.poolConfig;
-      };
-
-      # Reading the user-provided secret files requires root access
-      systemd.services.davis-env-setup = {
-        description = "Setup davis environment";
-        before = [
-          "phpfpm-davis.service"
-          "davis-db-migrate.service"
-        ];
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          Type = "oneshot";
-          RemainAfterExit = true;
-        };
-        path = [ pkgs.replace-secret ];
-        restartTriggers = [
-          cfg.package
-          davisEnv
-        ];
-        script = ''
-          # error handling
-          set -euo pipefail
-          # create .env file with the upstream values
-          install -T -m 0600 -o ${user} ${cfg.package}/env-upstream "${cfg.dataDir}/.env"
-          # create .env.local file with the user-provided values
-          install -T -m 0600 -o ${user} ${davisEnv} "${cfg.dataDir}/.env.local"
-          ${secretReplacements}
-        '';
-      };
-
-      systemd.services.davis-db-migrate = {
-        description = "Migrate davis database";
-        before = [ "phpfpm-davis.service" ];
-        after =
-          lib.optional mysqlLocal "mysql.service"
-          ++ lib.optional pgsqlLocal "postgresql.service"
-          ++ [ "davis-env-setup.service" ];
-        requires =
-          lib.optional mysqlLocal "mysql.service"
-          ++ lib.optional pgsqlLocal "postgresql.service"
-          ++ [ "davis-env-setup.service" ];
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = defaultServiceConfig // {
-          Type = "oneshot";
-          RemainAfterExit = true;
-          Environment = [
-            "ENV_DIR=${cfg.dataDir}"
-            "APP_CACHE_DIR=${cfg.dataDir}/var/cache"
-            "APP_LOG_DIR=${cfg.dataDir}/var/log"
-          ];
-          EnvironmentFile = "${cfg.dataDir}/.env.local";
-        };
-        restartTriggers = [
-          cfg.package
-          davisEnv
-        ];
-        script = ''
-          set -euo pipefail
-          ${cfg.package}/bin/console cache:clear --no-debug
-          ${cfg.package}/bin/console cache:warmup --no-debug
-          ${cfg.package}/bin/console doctrine:migrations:migrate
-        '';
-      };
-
-      systemd.services.phpfpm-davis.after = [
-        "davis-env-setup.service"
-        "davis-db-migrate.service"
-      ];
-      systemd.services.phpfpm-davis.requires = [
-        "davis-env-setup.service"
-        "davis-db-migrate.service"
-      ] ++ lib.optional mysqlLocal "mysql.service" ++ lib.optional pgsqlLocal "postgresql.service";
-      systemd.services.phpfpm-davis.serviceConfig.ReadWritePaths = [ cfg.dataDir ];
-
-      services.nginx = lib.mkIf (cfg.nginx != null) {
-        enable = lib.mkDefault true;
-        virtualHosts = {
-          "${cfg.hostname}" = lib.mkMerge [
-            cfg.nginx
-            {
-              root = lib.mkForce "${cfg.package}/public";
-              extraConfig = ''
-                charset utf-8;
-                index index.php;
-              '';
-              locations = {
-                "/" = {
-                  extraConfig = ''
-                    try_files $uri $uri/ /index.php$is_args$args;
-                  '';
-                };
-                "~* ^/.well-known/(caldav|carddav)$" = {
-                  extraConfig = ''
-                    return 302 $http_x_forwarded_proto://$host/dav/;
-                  '';
-                };
-                "~ ^(.+\.php)(.*)$" = {
-                  extraConfig = ''
-                    try_files                $fastcgi_script_name =404;
-                    include                  ${config.services.nginx.package}/conf/fastcgi_params;
-                    include                  ${config.services.nginx.package}/conf/fastcgi.conf;
-                    fastcgi_pass             unix:${config.services.phpfpm.pools.davis.socket};
-                    fastcgi_param            SCRIPT_FILENAME  $document_root$fastcgi_script_name;
-                    fastcgi_param            PATH_INFO        $fastcgi_path_info;
-                    fastcgi_split_path_info  ^(.+\.php)(.*)$;
-                    fastcgi_param            X-Forwarded-Proto $http_x_forwarded_proto;
-                    fastcgi_param            X-Forwarded-Port $http_x_forwarded_port;
-                  '';
-                };
-                "~ /(\\.ht)" = {
-                  extraConfig = ''
-                    deny all;
-                    return 404;
-                  '';
-                };
-              };
-            }
-          ];
-        };
-      };
-
-      services.mysql = lib.mkIf mysqlLocal {
-        enable = true;
-        package = lib.mkDefault pkgs.mariadb;
-        ensureDatabases = [ db.name ];
-        ensureUsers = [
-          {
-            name = user;
-            ensurePermissions = {
-              "${db.name}.*" = "ALL PRIVILEGES";
-            };
-          }
-        ];
-      };
-
-      services.postgresql = lib.mkIf pgsqlLocal {
-        enable = true;
-        ensureDatabases = [ db.name ];
-        ensureUsers = [
-          {
-            name = user;
-            ensureDBOwnership = true;
-          }
-        ];
-      };
-    };
-
-  meta = {
-    doc = ./davis.md;
-    maintainers = pkgs.davis.meta.maintainers;
-  };
-}
diff --git a/nixos/modules/services/web-apps/dex.nix b/nixos/modules/services/web-apps/dex.nix
deleted file mode 100644
index 7fbbd8a0c284..000000000000
--- a/nixos/modules/services/web-apps/dex.nix
+++ /dev/null
@@ -1,132 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.dex;
-  fixClient = client: if client ? secretFile then ((builtins.removeAttrs client [ "secretFile" ]) // { secret = client.secretFile; }) else client;
-  filteredSettings = mapAttrs (n: v: if n == "staticClients" then (builtins.map fixClient v) else v) cfg.settings;
-  secretFiles = flatten (builtins.map (c: optional (c ? secretFile) c.secretFile) (cfg.settings.staticClients or []));
-
-  settingsFormat = pkgs.formats.yaml {};
-  configFile = settingsFormat.generate "config.yaml" filteredSettings;
-
-  startPreScript = pkgs.writeShellScript "dex-start-pre"
-    (concatStringsSep "\n" (map (file: ''
-      replace-secret '${file}' '${file}' /run/dex/config.yaml
-    '')
-    secretFiles));
-in
-{
-  options.services.dex = {
-    enable = mkEnableOption "the OpenID Connect and OAuth2 identity provider";
-
-    environmentFile = mkOption {
-      type = types.nullOr types.path;
-      default = null;
-      description = ''
-        Environment file (see `systemd.exec(5)`
-        "EnvironmentFile=" section for the syntax) to define variables for dex.
-        This option can be used to safely include secret keys into the dex configuration.
-      '';
-    };
-
-    settings = mkOption {
-      type = settingsFormat.type;
-      default = {};
-      example = literalExpression ''
-        {
-          # External url
-          issuer = "http://127.0.0.1:5556/dex";
-          storage = {
-            type = "postgres";
-            config.host = "/var/run/postgres";
-          };
-          web = {
-            http = "127.0.0.1:5556";
-          };
-          enablePasswordDB = true;
-          staticClients = [
-            {
-              id = "oidcclient";
-              name = "Client";
-              redirectURIs = [ "https://example.com/callback" ];
-              secretFile = "/etc/dex/oidcclient"; # The content of `secretFile` will be written into to the config as `secret`.
-            }
-          ];
-        }
-      '';
-      description = ''
-        The available options can be found in
-        [the example configuration](https://github.com/dexidp/dex/blob/v${pkgs.dex-oidc.version}/config.yaml.dist).
-
-        It's also possible to refer to environment variables (defined in [services.dex.environmentFile](#opt-services.dex.environmentFile))
-        using the syntax `$VARIABLE_NAME`.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.dex = {
-      description = "dex identity provider";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "networking.target" ] ++ (optional (cfg.settings.storage.type == "postgres") "postgresql.service");
-      path = with pkgs; [ replace-secret ];
-      serviceConfig = {
-        ExecStart = "${pkgs.dex-oidc}/bin/dex serve /run/dex/config.yaml";
-        ExecStartPre = [
-          "${pkgs.coreutils}/bin/install -m 600 ${configFile} /run/dex/config.yaml"
-          "+${startPreScript}"
-        ];
-
-        RuntimeDirectory = "dex";
-        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
-        BindReadOnlyPaths = [
-          "/nix/store"
-          "-/etc/dex"
-          "-/etc/hosts"
-          "-/etc/localtime"
-          "-/etc/nsswitch.conf"
-          "-/etc/resolv.conf"
-          "-/etc/ssl/certs/ca-certificates.crt"
-        ];
-        BindPaths = optional (cfg.settings.storage.type == "postgres") "/var/run/postgresql";
-        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
-        # ProtectClock= adds DeviceAllow=char-rtc r
-        DeviceAllow = "";
-        DynamicUser = true;
-        LockPersonality = true;
-        MemoryDenyWriteExecute = true;
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateMounts = true;
-        # Port needs to be exposed to the host network
-        #PrivateNetwork = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        ProcSubset = "pid";
-        ProtectClock = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectSystem = "strict";
-        ProtectControlGroups = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged @setuid @keyring" ];
-        UMask = "0066";
-      } // optionalAttrs (cfg.environmentFile != null) {
-        EnvironmentFile = cfg.environmentFile;
-      };
-    };
-  };
-
-  # uses attributes of the linked package
-  meta.buildDocsInSandbox = false;
-}
diff --git a/nixos/modules/services/web-apps/discourse.md b/nixos/modules/services/web-apps/discourse.md
deleted file mode 100644
index d4b9c93c4ead..000000000000
--- a/nixos/modules/services/web-apps/discourse.md
+++ /dev/null
@@ -1,296 +0,0 @@
-# Discourse {#module-services-discourse}
-
-[Discourse](https://www.discourse.org/) is a
-modern and open source discussion platform.
-
-## Basic usage {#module-services-discourse-basic-usage}
-
-A minimal configuration using Let's Encrypt for TLS certificates looks like this:
-```nix
-{
-  services.discourse = {
-    enable = true;
-    hostname = "discourse.example.com";
-    admin = {
-      email = "admin@example.com";
-      username = "admin";
-      fullName = "Administrator";
-      passwordFile = "/path/to/password_file";
-    };
-    secretKeyBaseFile = "/path/to/secret_key_base_file";
-  };
-  security.acme.email = "me@example.com";
-  security.acme.acceptTerms = true;
-}
-```
-
-Provided a proper DNS setup, you'll be able to connect to the
-instance at `discourse.example.com` and log in
-using the credentials provided in
-`services.discourse.admin`.
-
-## Using a regular TLS certificate {#module-services-discourse-tls}
-
-To set up TLS using a regular certificate and key on file, use
-the [](#opt-services.discourse.sslCertificate)
-and [](#opt-services.discourse.sslCertificateKey)
-options:
-
-```nix
-{
-  services.discourse = {
-    enable = true;
-    hostname = "discourse.example.com";
-    sslCertificate = "/path/to/ssl_certificate";
-    sslCertificateKey = "/path/to/ssl_certificate_key";
-    admin = {
-      email = "admin@example.com";
-      username = "admin";
-      fullName = "Administrator";
-      passwordFile = "/path/to/password_file";
-    };
-    secretKeyBaseFile = "/path/to/secret_key_base_file";
-  };
-}
-```
-
-## Database access {#module-services-discourse-database}
-
-Discourse uses PostgreSQL to store most of its
-data. A database will automatically be enabled and a database
-and role created unless [](#opt-services.discourse.database.host) is changed from
-its default of `null` or [](#opt-services.discourse.database.createLocally) is set
-to `false`.
-
-External database access can also be configured by setting
-[](#opt-services.discourse.database.host),
-[](#opt-services.discourse.database.username) and
-[](#opt-services.discourse.database.passwordFile) as
-appropriate. Note that you need to manually create a database
-called `discourse` (or the name you chose in
-[](#opt-services.discourse.database.name)) and
-allow the configured database user full access to it.
-
-## Email {#module-services-discourse-mail}
-
-In addition to the basic setup, you'll want to configure an SMTP
-server Discourse can use to send user
-registration and password reset emails, among others. You can
-also optionally let Discourse receive
-email, which enables people to reply to threads and conversations
-via email.
-
-A basic setup which assumes you want to use your configured
-[hostname](#opt-services.discourse.hostname) as
-email domain can be done like this:
-
-```nix
-{
-  services.discourse = {
-    enable = true;
-    hostname = "discourse.example.com";
-    sslCertificate = "/path/to/ssl_certificate";
-    sslCertificateKey = "/path/to/ssl_certificate_key";
-    admin = {
-      email = "admin@example.com";
-      username = "admin";
-      fullName = "Administrator";
-      passwordFile = "/path/to/password_file";
-    };
-    mail.outgoing = {
-      serverAddress = "smtp.emailprovider.com";
-      port = 587;
-      username = "user@emailprovider.com";
-      passwordFile = "/path/to/smtp_password_file";
-    };
-    mail.incoming.enable = true;
-    secretKeyBaseFile = "/path/to/secret_key_base_file";
-  };
-}
-```
-
-This assumes you have set up an MX record for the address you've
-set in [hostname](#opt-services.discourse.hostname) and
-requires proper SPF, DKIM and DMARC configuration to be done for
-the domain you're sending from, in order for email to be reliably delivered.
-
-If you want to use a different domain for your outgoing email
-(for example `example.com` instead of
-`discourse.example.com`) you should set
-[](#opt-services.discourse.mail.notificationEmailAddress) and
-[](#opt-services.discourse.mail.contactEmailAddress) manually.
-
-::: {.note}
-Setup of TLS for incoming email is currently only configured
-automatically when a regular TLS certificate is used, i.e. when
-[](#opt-services.discourse.sslCertificate) and
-[](#opt-services.discourse.sslCertificateKey) are
-set.
-:::
-
-## Additional settings {#module-services-discourse-settings}
-
-Additional site settings and backend settings, for which no
-explicit NixOS options are provided,
-can be set in [](#opt-services.discourse.siteSettings) and
-[](#opt-services.discourse.backendSettings) respectively.
-
-### Site settings {#module-services-discourse-site-settings}
-
-"Site settings" are the settings that can be
-changed through the Discourse
-UI. Their *default* values can be set using
-[](#opt-services.discourse.siteSettings).
-
-Settings are expressed as a Nix attribute set which matches the
-structure of the configuration in
-[config/site_settings.yml](https://github.com/discourse/discourse/blob/master/config/site_settings.yml).
-To find a setting's path, you only need to care about the first
-two levels; i.e. its category (e.g. `login`)
-and name (e.g. `invite_only`).
-
-Settings containing secret data should be set to an attribute
-set containing the attribute `_secret` - a
-string pointing to a file containing the value the option
-should be set to. See the example.
-
-### Backend settings {#module-services-discourse-backend-settings}
-
-Settings are expressed as a Nix attribute set which matches the
-structure of the configuration in
-[config/discourse.conf](https://github.com/discourse/discourse/blob/stable/config/discourse_defaults.conf).
-Empty parameters can be defined by setting them to
-`null`.
-
-### Example {#module-services-discourse-settings-example}
-
-The following example sets the title and description of the
-Discourse instance and enables
-GitHub login in the site settings,
-and changes a few request limits in the backend settings:
-```nix
-{
-  services.discourse = {
-    enable = true;
-    hostname = "discourse.example.com";
-    sslCertificate = "/path/to/ssl_certificate";
-    sslCertificateKey = "/path/to/ssl_certificate_key";
-    admin = {
-      email = "admin@example.com";
-      username = "admin";
-      fullName = "Administrator";
-      passwordFile = "/path/to/password_file";
-    };
-    mail.outgoing = {
-      serverAddress = "smtp.emailprovider.com";
-      port = 587;
-      username = "user@emailprovider.com";
-      passwordFile = "/path/to/smtp_password_file";
-    };
-    mail.incoming.enable = true;
-    siteSettings = {
-      required = {
-        title = "My Cats";
-        site_description = "Discuss My Cats (and be nice plz)";
-      };
-      login = {
-        enable_github_logins = true;
-        github_client_id = "a2f6dfe838cb3206ce20";
-        github_client_secret._secret = /run/keys/discourse_github_client_secret;
-      };
-    };
-    backendSettings = {
-      max_reqs_per_ip_per_minute = 300;
-      max_reqs_per_ip_per_10_seconds = 60;
-      max_asset_reqs_per_ip_per_10_seconds = 250;
-      max_reqs_per_ip_mode = "warn+block";
-    };
-    secretKeyBaseFile = "/path/to/secret_key_base_file";
-  };
-}
-```
-
-In the resulting site settings file, the
-`login.github_client_secret` key will be set
-to the contents of the
-{file}`/run/keys/discourse_github_client_secret`
-file.
-
-## Plugins {#module-services-discourse-plugins}
-
-You can install Discourse plugins
-using the [](#opt-services.discourse.plugins)
-option. Pre-packaged plugins are provided in
-`<your_discourse_package_here>.plugins`. If
-you want the full suite of plugins provided through
-`nixpkgs`, you can also set the [](#opt-services.discourse.package) option to
-`pkgs.discourseAllPlugins`.
-
-Plugins can be built with the
-`<your_discourse_package_here>.mkDiscoursePlugin`
-function. Normally, it should suffice to provide a
-`name` and `src` attribute. If
-the plugin has Ruby dependencies, however, they need to be
-packaged in accordance with the [Developing with Ruby](https://nixos.org/manual/nixpkgs/stable/#developing-with-ruby)
-section of the Nixpkgs manual and the
-appropriate gem options set in `bundlerEnvArgs`
-(normally `gemdir` is sufficient). A plugin's
-Ruby dependencies are listed in its
-{file}`plugin.rb` file as function calls to
-`gem`. To construct the corresponding
-{file}`Gemfile` manually, run {command}`bundle init`, then add the `gem` lines to it
-verbatim.
-
-Much of the packaging can be done automatically by the
-{file}`nixpkgs/pkgs/servers/web-apps/discourse/update.py`
-script - just add the plugin to the `plugins`
-list in the `update_plugins` function and run
-the script:
-```bash
-./update.py update-plugins
-```
-
-Some plugins provide [site settings](#module-services-discourse-site-settings).
-Their defaults can be configured using [](#opt-services.discourse.siteSettings), just like
-regular site settings. To find the names of these settings, look
-in the `config/settings.yml` file of the plugin
-repo.
-
-For example, to add the [discourse-spoiler-alert](https://github.com/discourse/discourse-spoiler-alert)
-and [discourse-solved](https://github.com/discourse/discourse-solved)
-plugins, and disable `discourse-spoiler-alert`
-by default:
-
-```nix
-{
-  services.discourse = {
-    enable = true;
-    hostname = "discourse.example.com";
-    sslCertificate = "/path/to/ssl_certificate";
-    sslCertificateKey = "/path/to/ssl_certificate_key";
-    admin = {
-      email = "admin@example.com";
-      username = "admin";
-      fullName = "Administrator";
-      passwordFile = "/path/to/password_file";
-    };
-    mail.outgoing = {
-      serverAddress = "smtp.emailprovider.com";
-      port = 587;
-      username = "user@emailprovider.com";
-      passwordFile = "/path/to/smtp_password_file";
-    };
-    mail.incoming.enable = true;
-    plugins = with config.services.discourse.package.plugins; [
-      discourse-spoiler-alert
-      discourse-solved
-    ];
-    siteSettings = {
-      plugins = {
-        spoiler_enabled = false;
-      };
-    };
-    secretKeyBaseFile = "/path/to/secret_key_base_file";
-  };
-}
-```
diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix
deleted file mode 100644
index 849a03be8bc8..000000000000
--- a/nixos/modules/services/web-apps/discourse.nix
+++ /dev/null
@@ -1,1093 +0,0 @@
-{ config, options, lib, pkgs, utils, ... }:
-
-let
-  json = pkgs.formats.json {};
-
-  cfg = config.services.discourse;
-  opt = options.services.discourse;
-
-  # Keep in sync with https://github.com/discourse/discourse_docker/blob/main/image/base/slim.Dockerfile#L5
-  upstreamPostgresqlVersion = lib.getVersion pkgs.postgresql_13;
-
-  postgresqlPackage = if config.services.postgresql.enable then
-                        config.services.postgresql.package
-                      else
-                        pkgs.postgresql;
-
-  postgresqlVersion = lib.getVersion postgresqlPackage;
-
-  # We only want to create a database if we're actually going to connect to it.
-  databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == null;
-
-  tlsEnabled = cfg.enableACME
-                || cfg.sslCertificate != null
-                || cfg.sslCertificateKey != null;
-in
-{
-  options = {
-    services.discourse = {
-      enable = lib.mkEnableOption "Discourse, an open source discussion platform";
-
-      package = lib.mkOption {
-        type = lib.types.package;
-        default = pkgs.discourse;
-        apply = p: p.override {
-          plugins = lib.unique (p.enabledPlugins ++ cfg.plugins);
-        };
-        defaultText = lib.literalExpression "pkgs.discourse";
-        description = ''
-          The discourse package to use.
-        '';
-      };
-
-      hostname = lib.mkOption {
-        type = lib.types.str;
-        default = config.networking.fqdnOrHostName;
-        defaultText = lib.literalExpression "config.networking.fqdnOrHostName";
-        example = "discourse.example.com";
-        description = ''
-          The hostname to serve Discourse on.
-        '';
-      };
-
-      secretKeyBaseFile = lib.mkOption {
-        type = with lib.types; nullOr path;
-        default = null;
-        example = "/run/keys/secret_key_base";
-        description = ''
-          The path to a file containing the
-          `secret_key_base` secret.
-
-          Discourse uses `secret_key_base` to encrypt
-          the cookie store, which contains session data, and to digest
-          user auth tokens.
-
-          Needs to be a 64 byte long string of hexadecimal
-          characters. You can generate one by running
-
-          ```
-          openssl rand -hex 64 >/path/to/secret_key_base_file
-          ```
-
-          This should be a string, not a nix path, since nix paths are
-          copied into the world-readable nix store.
-        '';
-      };
-
-      sslCertificate = lib.mkOption {
-        type = with lib.types; nullOr path;
-        default = null;
-        example = "/run/keys/ssl.cert";
-        description = ''
-          The path to the server SSL certificate. Set this to enable
-          SSL.
-        '';
-      };
-
-      sslCertificateKey = lib.mkOption {
-        type = with lib.types; nullOr path;
-        default = null;
-        example = "/run/keys/ssl.key";
-        description = ''
-          The path to the server SSL certificate key. Set this to
-          enable SSL.
-        '';
-      };
-
-      enableACME = lib.mkOption {
-        type = lib.types.bool;
-        default = cfg.sslCertificate == null && cfg.sslCertificateKey == null;
-        defaultText = lib.literalMD ''
-          `true`, unless {option}`services.discourse.sslCertificate`
-          and {option}`services.discourse.sslCertificateKey` are set.
-        '';
-        description = ''
-          Whether an ACME certificate should be used to secure
-          connections to the server.
-        '';
-      };
-
-      backendSettings = lib.mkOption {
-        type = with lib.types; attrsOf (nullOr (oneOf [ str int bool float ]));
-        default = {};
-        example = lib.literalExpression ''
-          {
-            max_reqs_per_ip_per_minute = 300;
-            max_reqs_per_ip_per_10_seconds = 60;
-            max_asset_reqs_per_ip_per_10_seconds = 250;
-            max_reqs_per_ip_mode = "warn+block";
-          };
-        '';
-        description = ''
-          Additional settings to put in the
-          {file}`discourse.conf` file.
-
-          Look in the
-          [discourse_defaults.conf](https://github.com/discourse/discourse/blob/master/config/discourse_defaults.conf)
-          file in the upstream distribution to find available options.
-
-          Setting an option to `null` means
-          “define variable, but leave right-hand side empty”.
-        '';
-      };
-
-      siteSettings = lib.mkOption {
-        type = json.type;
-        default = {};
-        example = lib.literalExpression ''
-          {
-            required = {
-              title = "My Cats";
-              site_description = "Discuss My Cats (and be nice plz)";
-            };
-            login = {
-              enable_github_logins = true;
-              github_client_id = "a2f6dfe838cb3206ce20";
-              github_client_secret._secret = /run/keys/discourse_github_client_secret;
-            };
-          };
-        '';
-        description = ''
-          Discourse site settings. These are the settings that can be
-          changed from the UI. This only defines their default values:
-          they can still be overridden from the UI.
-
-          Available settings can be found by looking in the
-          [site_settings.yml](https://github.com/discourse/discourse/blob/master/config/site_settings.yml)
-          file of the upstream distribution. To find a setting's path,
-          you only need to care about the first two levels; i.e. its
-          category and name. See the example.
-
-          Settings containing secret data should be set to an
-          attribute set containing the attribute
-          `_secret` - a string pointing to a file
-          containing the value the option should be set to. See the
-          example to get a better picture of this: in the resulting
-          {file}`config/nixos_site_settings.json` file,
-          the `login.github_client_secret` key will
-          be set to the contents of the
-          {file}`/run/keys/discourse_github_client_secret`
-          file.
-        '';
-      };
-
-      admin = {
-        skipCreate = lib.mkOption {
-          type = lib.types.bool;
-          default = false;
-          description = ''
-            Do not create the admin account, instead rely on other
-            existing admin accounts.
-          '';
-        };
-
-        email = lib.mkOption {
-          type = lib.types.str;
-          example = "admin@example.com";
-          description = ''
-            The admin user email address.
-          '';
-        };
-
-        username = lib.mkOption {
-          type = lib.types.str;
-          example = "admin";
-          description = ''
-            The admin user username.
-          '';
-        };
-
-        fullName = lib.mkOption {
-          type = lib.types.str;
-          description = ''
-            The admin user's full name.
-          '';
-        };
-
-        passwordFile = lib.mkOption {
-          type = lib.types.path;
-          description = ''
-            A path to a file containing the admin user's password.
-
-            This should be a string, not a nix path, since nix paths are
-            copied into the world-readable nix store.
-          '';
-        };
-      };
-
-      nginx.enable = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = ''
-          Whether an `nginx` virtual host should be
-          set up to serve Discourse. Only disable if you're planning
-          to use a different web server, which is not recommended.
-        '';
-      };
-
-      database = {
-        pool = lib.mkOption {
-          type = lib.types.int;
-          default = 8;
-          description = ''
-            Database connection pool size.
-          '';
-        };
-
-        host = lib.mkOption {
-          type = with lib.types; nullOr str;
-          default = null;
-          description = ''
-            Discourse database hostname. `null` means
-            “prefer local unix socket connection”.
-          '';
-        };
-
-        passwordFile = lib.mkOption {
-          type = with lib.types; nullOr path;
-          default = null;
-          description = ''
-            File containing the Discourse database user password.
-
-            This should be a string, not a nix path, since nix paths are
-            copied into the world-readable nix store.
-          '';
-        };
-
-        createLocally = lib.mkOption {
-          type = lib.types.bool;
-          default = true;
-          description = ''
-            Whether a database should be automatically created on the
-            local host. Set this to `false` if you plan
-            on provisioning a local database yourself. This has no effect
-            if {option}`services.discourse.database.host` is customized.
-          '';
-        };
-
-        name = lib.mkOption {
-          type = lib.types.str;
-          default = "discourse";
-          description = ''
-            Discourse database name.
-          '';
-        };
-
-        username = lib.mkOption {
-          type = lib.types.str;
-          default = "discourse";
-          description = ''
-            Discourse database user.
-          '';
-        };
-
-        ignorePostgresqlVersion = lib.mkOption {
-          type = lib.types.bool;
-          default = false;
-          description = ''
-            Whether to allow other versions of PostgreSQL than the
-            recommended one. Only effective when
-            {option}`services.discourse.database.createLocally`
-            is enabled.
-          '';
-        };
-      };
-
-      redis = {
-        host = lib.mkOption {
-          type = lib.types.str;
-          default = "localhost";
-          description = ''
-            Redis server hostname.
-          '';
-        };
-
-        passwordFile = lib.mkOption {
-          type = with lib.types; nullOr path;
-          default = null;
-          description = ''
-            File containing the Redis password.
-
-            This should be a string, not a nix path, since nix paths are
-            copied into the world-readable nix store.
-          '';
-        };
-
-        dbNumber = lib.mkOption {
-          type = lib.types.int;
-          default = 0;
-          description = ''
-            Redis database number.
-          '';
-        };
-
-        useSSL = lib.mkOption {
-          type = lib.types.bool;
-          default = cfg.redis.host != "localhost";
-          defaultText = lib.literalExpression ''config.${opt.redis.host} != "localhost"'';
-          description = ''
-            Connect to Redis with SSL.
-          '';
-        };
-      };
-
-      mail = {
-        notificationEmailAddress = lib.mkOption {
-          type = lib.types.str;
-          default = "${if cfg.mail.incoming.enable then "notifications" else "noreply"}@${cfg.hostname}";
-          defaultText = lib.literalExpression ''
-            "''${if config.services.discourse.mail.incoming.enable then "notifications" else "noreply"}@''${config.services.discourse.hostname}"
-          '';
-          description = ''
-            The `from:` email address used when
-            sending all essential system emails. The domain specified
-            here must have SPF, DKIM and reverse PTR records set
-            correctly for email to arrive.
-          '';
-        };
-
-        contactEmailAddress = lib.mkOption {
-          type = lib.types.str;
-          default = "";
-          description = ''
-            Email address of key contact responsible for this
-            site. Used for critical notifications, as well as on the
-            `/about` contact form for urgent matters.
-          '';
-        };
-
-        outgoing = {
-          serverAddress = lib.mkOption {
-            type = lib.types.str;
-            default = "localhost";
-            description = ''
-              The address of the SMTP server Discourse should use to
-              send email.
-            '';
-          };
-
-          port = lib.mkOption {
-            type = lib.types.port;
-            default = 25;
-            description = ''
-              The port of the SMTP server Discourse should use to
-              send email.
-            '';
-          };
-
-          username = lib.mkOption {
-            type = with lib.types; nullOr str;
-            default = null;
-            description = ''
-              The username of the SMTP server.
-            '';
-          };
-
-          passwordFile = lib.mkOption {
-            type = lib.types.nullOr lib.types.path;
-            default = null;
-            description = ''
-              A file containing the password of the SMTP server account.
-
-              This should be a string, not a nix path, since nix paths
-              are copied into the world-readable nix store.
-            '';
-          };
-
-          domain = lib.mkOption {
-            type = lib.types.str;
-            default = cfg.hostname;
-            defaultText = lib.literalExpression "config.${opt.hostname}";
-            description = ''
-              HELO domain to use for outgoing mail.
-            '';
-          };
-
-          authentication = lib.mkOption {
-            type = with lib.types; nullOr (enum ["plain" "login" "cram_md5"]);
-            default = null;
-            description = ''
-              Authentication type to use, see https://api.rubyonrails.org/classes/ActionMailer/Base.html
-            '';
-          };
-
-          enableStartTLSAuto = lib.mkOption {
-            type = lib.types.bool;
-            default = true;
-            description = ''
-              Whether to try to use StartTLS.
-            '';
-          };
-
-          opensslVerifyMode = lib.mkOption {
-            type = lib.types.str;
-            default = "peer";
-            description = ''
-              How OpenSSL checks the certificate, see https://api.rubyonrails.org/classes/ActionMailer/Base.html
-            '';
-          };
-
-          forceTLS = lib.mkOption {
-            type = lib.types.bool;
-            default = false;
-            description = ''
-              Force implicit TLS as per RFC 8314 3.3.
-            '';
-          };
-        };
-
-        incoming = {
-          enable = lib.mkOption {
-            type = lib.types.bool;
-            default = false;
-            description = ''
-              Whether to set up Postfix to receive incoming mail.
-            '';
-          };
-
-          replyEmailAddress = lib.mkOption {
-            type = lib.types.str;
-            default = "%{reply_key}@${cfg.hostname}";
-            defaultText = lib.literalExpression ''"%{reply_key}@''${config.services.discourse.hostname}"'';
-            description = ''
-              Template for reply by email incoming email address, for
-              example: %{reply_key}@reply.example.com or
-              replies+%{reply_key}@example.com
-            '';
-          };
-
-          mailReceiverPackage = lib.mkOption {
-            type = lib.types.package;
-            default = pkgs.discourse-mail-receiver;
-            defaultText = lib.literalExpression "pkgs.discourse-mail-receiver";
-            description = ''
-              The discourse-mail-receiver package to use.
-            '';
-          };
-
-          apiKeyFile = lib.mkOption {
-            type = lib.types.nullOr lib.types.path;
-            default = null;
-            description = ''
-              A file containing the Discourse API key used to add
-              posts and messages from mail. If left at its default
-              value `null`, one will be automatically
-              generated.
-
-              This should be a string, not a nix path, since nix paths
-              are copied into the world-readable nix store.
-            '';
-          };
-        };
-      };
-
-      plugins = lib.mkOption {
-        type = lib.types.listOf lib.types.package;
-        default = [];
-        example = lib.literalExpression ''
-          with config.services.discourse.package.plugins; [
-            discourse-canned-replies
-            discourse-github
-          ];
-        '';
-        description = ''
-          Plugins to install as part of Discourse, expressed as a list of derivations.
-        '';
-      };
-
-      sidekiqProcesses = lib.mkOption {
-        type = lib.types.int;
-        default = 1;
-        description = ''
-          How many Sidekiq processes should be spawned.
-        '';
-      };
-
-      unicornTimeout = lib.mkOption {
-        type = lib.types.int;
-        default = 30;
-        description = ''
-          Time in seconds before a request to Unicorn times out.
-
-          This can be raised if the system Discourse is running on is
-          too slow to handle many requests within 30 seconds.
-        '';
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = (cfg.database.host != null) -> (cfg.database.passwordFile != null);
-        message = "When services.gitlab.database.host is customized, services.discourse.database.passwordFile must be set!";
-      }
-      {
-        assertion = cfg.hostname != "";
-        message = "Could not automatically determine hostname, set service.discourse.hostname manually.";
-      }
-      {
-        assertion = cfg.database.ignorePostgresqlVersion || (databaseActuallyCreateLocally -> upstreamPostgresqlVersion == postgresqlVersion);
-        message = "The PostgreSQL version recommended for use with Discourse is ${upstreamPostgresqlVersion}, you're using ${postgresqlVersion}. "
-                  + "Either update your PostgreSQL package to the correct version or set services.discourse.database.ignorePostgresqlVersion. "
-                  + "See https://nixos.org/manual/nixos/stable/index.html#module-postgresql for details on how to upgrade PostgreSQL.";
-      }
-    ];
-
-
-    # Default config values are from `config/discourse_defaults.conf`
-    # upstream.
-    services.discourse.backendSettings = lib.mapAttrs (_: lib.mkDefault) {
-      db_pool = cfg.database.pool;
-      db_timeout = 5000;
-      db_connect_timeout = 5;
-      db_socket = null;
-      db_host = cfg.database.host;
-      db_backup_host = null;
-      db_port = null;
-      db_backup_port = 5432;
-      db_name = cfg.database.name;
-      db_username = if databaseActuallyCreateLocally then "discourse" else cfg.database.username;
-      db_password = cfg.database.passwordFile;
-      db_prepared_statements = false;
-      db_replica_host = null;
-      db_replica_port = null;
-      db_advisory_locks = true;
-
-      inherit (cfg) hostname;
-      backup_hostname = null;
-
-      smtp_address = cfg.mail.outgoing.serverAddress;
-      smtp_port = cfg.mail.outgoing.port;
-      smtp_domain = cfg.mail.outgoing.domain;
-      smtp_user_name = cfg.mail.outgoing.username;
-      smtp_password = cfg.mail.outgoing.passwordFile;
-      smtp_authentication = cfg.mail.outgoing.authentication;
-      smtp_enable_start_tls = cfg.mail.outgoing.enableStartTLSAuto;
-      smtp_openssl_verify_mode = cfg.mail.outgoing.opensslVerifyMode;
-      smtp_force_tls = cfg.mail.outgoing.forceTLS;
-
-      load_mini_profiler = true;
-      mini_profiler_snapshots_period = 0;
-      mini_profiler_snapshots_transport_url = null;
-      mini_profiler_snapshots_transport_auth_key = null;
-
-      cdn_url = null;
-      cdn_origin_hostname = null;
-      developer_emails = null;
-
-      redis_host = cfg.redis.host;
-      redis_port = 6379;
-      redis_replica_host = null;
-      redis_replica_port = 6379;
-      redis_db = cfg.redis.dbNumber;
-      redis_password = cfg.redis.passwordFile;
-      redis_skip_client_commands = false;
-      redis_use_ssl = cfg.redis.useSSL;
-
-      message_bus_redis_enabled = false;
-      message_bus_redis_host = "localhost";
-      message_bus_redis_port = 6379;
-      message_bus_redis_replica_host = null;
-      message_bus_redis_replica_port = 6379;
-      message_bus_redis_db = 0;
-      message_bus_redis_password = null;
-      message_bus_redis_skip_client_commands = false;
-
-      enable_cors = false;
-      cors_origin = "";
-      serve_static_assets = false;
-      sidekiq_workers = 5;
-      connection_reaper_age = 30;
-      connection_reaper_interval = 30;
-      relative_url_root = null;
-      message_bus_max_backlog_size = 100;
-      message_bus_clear_every = 50;
-      secret_key_base = cfg.secretKeyBaseFile;
-      fallback_assets_path = null;
-
-      s3_bucket = null;
-      s3_region = null;
-      s3_access_key_id = null;
-      s3_secret_access_key = null;
-      s3_use_iam_profile = null;
-      s3_cdn_url = null;
-      s3_endpoint = null;
-      s3_http_continue_timeout = null;
-      s3_install_cors_rule = null;
-      s3_asset_cdn_url = null;
-
-      max_user_api_reqs_per_minute = 20;
-      max_user_api_reqs_per_day = 2880;
-      max_admin_api_reqs_per_minute = 60;
-      max_reqs_per_ip_per_minute = 200;
-      max_reqs_per_ip_per_10_seconds = 50;
-      max_asset_reqs_per_ip_per_10_seconds = 200;
-      max_reqs_per_ip_mode = "block";
-      max_reqs_rate_limit_on_private = false;
-      skip_per_ip_rate_limit_trust_level = 1;
-      force_anonymous_min_queue_seconds = 1;
-      force_anonymous_min_per_10_seconds = 3;
-      background_requests_max_queue_length = 0.5;
-      reject_message_bus_queue_seconds = 0.1;
-      disable_search_queue_threshold = 1;
-      max_old_rebakes_per_15_minutes = 300;
-      max_logster_logs = 1000;
-      refresh_maxmind_db_during_precompile_days = 2;
-      maxmind_backup_path = null;
-      maxmind_license_key = null;
-      enable_performance_http_headers = false;
-      enable_js_error_reporting = true;
-      mini_scheduler_workers = 5;
-      compress_anon_cache = false;
-      anon_cache_store_threshold = 2;
-      allowed_theme_repos = null;
-      enable_email_sync_demon = false;
-      max_digests_enqueued_per_30_mins_per_site = 10000;
-      cluster_name = null;
-      multisite_config_path = "config/multisite.yml";
-      enable_long_polling = null;
-      long_polling_interval = null;
-      preload_link_header = false;
-      redirect_avatar_requests = false;
-      pg_force_readonly_mode = false;
-      dns_query_timeout_secs = null;
-      regex_timeout_seconds = 2;
-      allow_impersonation = true;
-    };
-
-    services.redis.servers.discourse =
-      lib.mkIf (lib.elem cfg.redis.host [ "localhost" "127.0.0.1" ]) {
-        enable = true;
-        bind = cfg.redis.host;
-        port = cfg.backendSettings.redis_port;
-      };
-
-    services.postgresql = lib.mkIf databaseActuallyCreateLocally {
-      enable = true;
-      ensureUsers = [{ name = "discourse"; }];
-    };
-
-    # The postgresql module doesn't currently support concepts like
-    # objects owners and extensions; for now we tack on what's needed
-    # here.
-    systemd.services.discourse-postgresql =
-      let
-        pgsql = config.services.postgresql;
-      in
-        lib.mkIf databaseActuallyCreateLocally {
-          after = [ "postgresql.service" ];
-          bindsTo = [ "postgresql.service" ];
-          wantedBy = [ "discourse.service" ];
-          partOf = [ "discourse.service" ];
-          path = [
-            pgsql.package
-          ];
-          script = ''
-            set -o errexit -o pipefail -o nounset -o errtrace
-            shopt -s inherit_errexit
-
-            psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'discourse'" | grep -q 1 || psql -tAc 'CREATE DATABASE "discourse" OWNER "discourse"'
-            psql '${cfg.database.name}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
-            psql '${cfg.database.name}' -tAc "CREATE EXTENSION IF NOT EXISTS hstore"
-          '';
-
-          serviceConfig = {
-            User = pgsql.superUser;
-            Type = "oneshot";
-            RemainAfterExit = true;
-          };
-        };
-
-    systemd.services.discourse = {
-      wantedBy = [ "multi-user.target" ];
-      after = [
-        "redis-discourse.service"
-        "postgresql.service"
-        "discourse-postgresql.service"
-      ];
-      bindsTo = [
-        "redis-discourse.service"
-      ] ++ lib.optionals (cfg.database.host == null) [
-        "postgresql.service"
-        "discourse-postgresql.service"
-      ];
-      path = cfg.package.runtimeDeps ++ [
-        postgresqlPackage
-        pkgs.replace-secret
-        cfg.package.rake
-      ];
-      environment = cfg.package.runtimeEnv // {
-        UNICORN_TIMEOUT = builtins.toString cfg.unicornTimeout;
-        UNICORN_SIDEKIQS = builtins.toString cfg.sidekiqProcesses;
-        MALLOC_ARENA_MAX = "2";
-      };
-
-      preStart =
-        let
-          discourseKeyValue = lib.generators.toKeyValue {
-            mkKeyValue = lib.flip lib.generators.mkKeyValueDefault " = " {
-              mkValueString = v: with builtins;
-                if isInt           v then toString v
-                else if isString   v then ''"${v}"''
-                else if true  ==   v then "true"
-                else if false ==   v then "false"
-                else if null  ==   v then ""
-                else if isFloat    v then lib.strings.floatToString v
-                else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
-            };
-          };
-
-          discourseConf = pkgs.writeText "discourse.conf" (discourseKeyValue cfg.backendSettings);
-
-          mkSecretReplacement = file:
-            lib.optionalString (file != null) ''
-              replace-secret '${file}' '${file}' /run/discourse/config/discourse.conf
-            '';
-
-          mkAdmin = ''
-            export ADMIN_EMAIL="${cfg.admin.email}"
-            export ADMIN_NAME="${cfg.admin.fullName}"
-            export ADMIN_USERNAME="${cfg.admin.username}"
-            ADMIN_PASSWORD="$(<${cfg.admin.passwordFile})"
-            export ADMIN_PASSWORD
-            discourse-rake admin:create_noninteractively
-          '';
-
-        in ''
-          set -o errexit -o pipefail -o nounset -o errtrace
-          shopt -s inherit_errexit
-
-          umask u=rwx,g=rx,o=
-
-          rm -rf /var/lib/discourse/tmp/*
-
-          cp -r ${cfg.package}/share/discourse/config.dist/* /run/discourse/config/
-          cp -r ${cfg.package}/share/discourse/public.dist/* /run/discourse/public/
-          ln -sf /var/lib/discourse/uploads /run/discourse/public/uploads
-          ln -sf /var/lib/discourse/backups /run/discourse/public/backups
-
-          (
-              umask u=rwx,g=,o=
-
-              ${utils.genJqSecretsReplacementSnippet
-                  cfg.siteSettings
-                  "/run/discourse/config/nixos_site_settings.json"
-              }
-              install -T -m 0600 -o discourse ${discourseConf} /run/discourse/config/discourse.conf
-              ${mkSecretReplacement cfg.database.passwordFile}
-              ${mkSecretReplacement cfg.mail.outgoing.passwordFile}
-              ${mkSecretReplacement cfg.redis.passwordFile}
-              ${mkSecretReplacement cfg.secretKeyBaseFile}
-              chmod 0400 /run/discourse/config/discourse.conf
-          )
-
-          discourse-rake db:migrate >>/var/log/discourse/db_migration.log
-          chmod -R u+w /var/lib/discourse/tmp/
-
-          ${lib.optionalString (!cfg.admin.skipCreate) mkAdmin}
-
-          discourse-rake themes:update
-          discourse-rake uploads:regenerate_missing_optimized
-        '';
-
-      serviceConfig = {
-        Type = "simple";
-        User = "discourse";
-        Group = "discourse";
-        RuntimeDirectory = map (p: "discourse/" + p) [
-          "config"
-          "home"
-          "assets/javascripts/plugins"
-          "public"
-          "sockets"
-        ];
-        RuntimeDirectoryMode = "0750";
-        StateDirectory = map (p: "discourse/" + p) [
-          "uploads"
-          "backups"
-          "tmp"
-        ];
-        StateDirectoryMode = "0750";
-        LogsDirectory = "discourse";
-        TimeoutSec = "infinity";
-        Restart = "on-failure";
-        WorkingDirectory = "${cfg.package}/share/discourse";
-
-        RemoveIPC = true;
-        PrivateTmp = true;
-        NoNewPrivileges = true;
-        RestrictSUIDSGID = true;
-        ProtectSystem = "strict";
-        ProtectHome = "read-only";
-
-        ExecStart = "${cfg.package.rubyEnv}/bin/bundle exec config/unicorn_launcher -E production -c config/unicorn.conf.rb";
-      };
-    };
-
-    services.nginx = lib.mkIf cfg.nginx.enable {
-      enable = true;
-
-      recommendedTlsSettings = true;
-      recommendedOptimisation = true;
-      recommendedBrotliSettings = true;
-      recommendedGzipSettings = true;
-      recommendedProxySettings = true;
-
-      upstreams.discourse.servers."unix:/run/discourse/sockets/unicorn.sock" = {};
-
-      appendHttpConfig = ''
-        # inactive means we keep stuff around for 1440m minutes regardless of last access (1 week)
-        # levels means it is a 2 deep hierarchy cause we can have lots of files
-        # max_size limits the size of the cache
-        proxy_cache_path /var/cache/nginx inactive=1440m levels=1:2 keys_zone=discourse:10m max_size=600m;
-
-        # see: https://meta.discourse.org/t/x/74060
-        proxy_buffer_size 8k;
-      '';
-
-      virtualHosts.${cfg.hostname} = {
-        inherit (cfg) sslCertificate sslCertificateKey enableACME;
-        forceSSL = lib.mkDefault tlsEnabled;
-
-        root = "${cfg.package}/share/discourse/public";
-
-        locations =
-          let
-            proxy = { extraConfig ? "" }: {
-              proxyPass = "http://discourse";
-              extraConfig = extraConfig + ''
-                proxy_set_header X-Request-Start "t=''${msec}";
-              '';
-            };
-            cache = time: ''
-              expires ${time};
-              add_header Cache-Control public,immutable;
-            '';
-            cache_1y = cache "1y";
-            cache_1d = cache "1d";
-          in
-            {
-              "/".tryFiles = "$uri @discourse";
-              "@discourse" = proxy {};
-              "^~ /backups/".extraConfig = ''
-                internal;
-              '';
-              "/favicon.ico" = {
-                return = "204";
-                extraConfig = ''
-                  access_log off;
-                  log_not_found off;
-                '';
-              };
-              "~ ^/uploads/short-url/" = proxy {};
-              "~ ^/secure-media-uploads/" = proxy {};
-              "~* (fonts|assets|plugins|uploads)/.*\.(eot|ttf|woff|woff2|ico|otf)$".extraConfig = cache_1y + ''
-                add_header Access-Control-Allow-Origin *;
-              '';
-              "/srv/status" = proxy {
-                extraConfig = ''
-                  access_log off;
-                  log_not_found off;
-                '';
-              };
-              "~ ^/javascripts/".extraConfig = cache_1d;
-              "~ ^/assets/(?<asset_path>.+)$".extraConfig = cache_1y + ''
-                # asset pipeline enables this
-                brotli_static on;
-                gzip_static on;
-              '';
-              "~ ^/plugins/".extraConfig = cache_1y;
-              "~ /images/emoji/".extraConfig = cache_1y;
-              "~ ^/uploads/" = proxy {
-                extraConfig = cache_1y + ''
-                  proxy_set_header X-Sendfile-Type X-Accel-Redirect;
-                  proxy_set_header X-Accel-Mapping ${cfg.package}/share/discourse/public/=/downloads/;
-
-                  # custom CSS
-                  location ~ /stylesheet-cache/ {
-                      try_files $uri =404;
-                  }
-                  # this allows us to bypass rails
-                  location ~* \.(gif|png|jpg|jpeg|bmp|tif|tiff|ico|webp)$ {
-                      try_files $uri =404;
-                  }
-                  # SVG needs an extra header attached
-                  location ~* \.(svg)$ {
-                  }
-                  # thumbnails & optimized images
-                  location ~ /_?optimized/ {
-                      try_files $uri =404;
-                  }
-                '';
-              };
-              "~ ^/admin/backups/" = proxy {
-                extraConfig = ''
-                  proxy_set_header X-Sendfile-Type X-Accel-Redirect;
-                  proxy_set_header X-Accel-Mapping ${cfg.package}/share/discourse/public/=/downloads/;
-                '';
-              };
-              "~ ^/(svg-sprite/|letter_avatar/|letter_avatar_proxy/|user_avatar|highlight-js|stylesheets|theme-javascripts|favicon/proxied|service-worker)" = proxy {
-                extraConfig = ''
-                  # if Set-Cookie is in the response nothing gets cached
-                  # this is double bad cause we are not passing last modified in
-                  proxy_ignore_headers "Set-Cookie";
-                  proxy_hide_header "Set-Cookie";
-                  proxy_hide_header "X-Discourse-Username";
-                  proxy_hide_header "X-Runtime";
-
-                  # note x-accel-redirect can not be used with proxy_cache
-                  proxy_cache discourse;
-                  proxy_cache_key "$scheme,$host,$request_uri";
-                  proxy_cache_valid 200 301 302 7d;
-                '';
-              };
-              "/message-bus/" = proxy {
-                extraConfig = ''
-                  proxy_http_version 1.1;
-                  proxy_buffering off;
-                '';
-              };
-              "/downloads/".extraConfig = ''
-                internal;
-                alias ${cfg.package}/share/discourse/public/;
-              '';
-            };
-      };
-    };
-
-    systemd.services.discourse-mail-receiver-setup = lib.mkIf cfg.mail.incoming.enable (
-      let
-        mail-receiver-environment = {
-          MAIL_DOMAIN = cfg.hostname;
-          DISCOURSE_BASE_URL = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
-          DISCOURSE_API_KEY = "@api-key@";
-          DISCOURSE_API_USERNAME = "system";
-        };
-        mail-receiver-json = json.generate "mail-receiver.json" mail-receiver-environment;
-      in
-        {
-          before = [ "postfix.service" ];
-          after = [ "discourse.service" ];
-          wantedBy = [ "discourse.service" ];
-          partOf = [ "discourse.service" ];
-          path = [
-            cfg.package.rake
-            pkgs.jq
-          ];
-          preStart = lib.optionalString (cfg.mail.incoming.apiKeyFile == null) ''
-            set -o errexit -o pipefail -o nounset -o errtrace
-            shopt -s inherit_errexit
-
-            if [[ ! -e /var/lib/discourse-mail-receiver/api_key ]]; then
-                discourse-rake api_key:create_master[email-receiver] >/var/lib/discourse-mail-receiver/api_key
-            fi
-          '';
-          script =
-            let
-              apiKeyPath =
-                if cfg.mail.incoming.apiKeyFile == null then
-                  "/var/lib/discourse-mail-receiver/api_key"
-                else
-                  cfg.mail.incoming.apiKeyFile;
-            in ''
-              set -o errexit -o pipefail -o nounset -o errtrace
-              shopt -s inherit_errexit
-
-              api_key=$(<'${apiKeyPath}')
-              export api_key
-
-              jq <${mail-receiver-json} \
-                 '.DISCOURSE_API_KEY = $ENV.api_key' \
-                 >'/run/discourse-mail-receiver/mail-receiver-environment.json'
-            '';
-
-          serviceConfig = {
-            Type = "oneshot";
-            RemainAfterExit = true;
-            RuntimeDirectory = "discourse-mail-receiver";
-            RuntimeDirectoryMode = "0700";
-            StateDirectory = "discourse-mail-receiver";
-            User = "discourse";
-            Group = "discourse";
-          };
-        });
-
-    services.discourse.siteSettings = {
-      required = {
-        notification_email = cfg.mail.notificationEmailAddress;
-        contact_email = cfg.mail.contactEmailAddress;
-      };
-      security.force_https = tlsEnabled;
-      email = {
-        manual_polling_enabled = cfg.mail.incoming.enable;
-        reply_by_email_enabled = cfg.mail.incoming.enable;
-        reply_by_email_address = cfg.mail.incoming.replyEmailAddress;
-      };
-    };
-
-    services.postfix = lib.mkIf cfg.mail.incoming.enable {
-      enable = true;
-      sslCert = lib.optionalString (cfg.sslCertificate != null) cfg.sslCertificate;
-      sslKey = lib.optionalString (cfg.sslCertificateKey != null) cfg.sslCertificateKey;
-
-      origin = cfg.hostname;
-      relayDomains = [ cfg.hostname ];
-      config = {
-        smtpd_recipient_restrictions = "check_policy_service unix:private/discourse-policy";
-        append_dot_mydomain = lib.mkDefault false;
-        compatibility_level = "2";
-        smtputf8_enable = false;
-        smtpd_banner = lib.mkDefault "ESMTP server";
-        myhostname = lib.mkDefault cfg.hostname;
-        mydestination = lib.mkDefault "localhost";
-      };
-      transport = ''
-        ${cfg.hostname} discourse-mail-receiver:
-      '';
-      masterConfig = {
-        "discourse-mail-receiver" = {
-          type = "unix";
-          privileged = true;
-          chroot = false;
-          command = "pipe";
-          args = [
-            "user=discourse"
-            "argv=${cfg.mail.incoming.mailReceiverPackage}/bin/receive-mail"
-            "\${recipient}"
-          ];
-        };
-        "discourse-policy" = {
-          type = "unix";
-          privileged = true;
-          chroot = false;
-          command = "spawn";
-          args = [
-            "user=discourse"
-            "argv=${cfg.mail.incoming.mailReceiverPackage}/bin/discourse-smtp-fast-rejection"
-          ];
-        };
-      };
-    };
-
-    users.users = {
-      discourse = {
-        group = "discourse";
-        isSystemUser = true;
-      };
-    } // (lib.optionalAttrs cfg.nginx.enable {
-      ${config.services.nginx.user}.extraGroups = [ "discourse" ];
-    });
-
-    users.groups = {
-      discourse = {};
-    };
-
-    environment.systemPackages = [
-      cfg.package.rake
-    ];
-  };
-
-  meta.doc = ./discourse.md;
-  meta.maintainers = [ lib.maintainers.talyz ];
-}
diff --git a/nixos/modules/services/web-apps/documize.nix b/nixos/modules/services/web-apps/documize.nix
deleted file mode 100644
index ab6d08f05992..000000000000
--- a/nixos/modules/services/web-apps/documize.nix
+++ /dev/null
@@ -1,130 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-with lib;
-
-let
-  cfg = config.services.documize;
-
-  mkParams = optional: concatMapStrings (name: let
-    predicate = optional -> cfg.${name} != null;
-    template = " -${name} '${toString cfg.${name}}'";
-  in optionalString predicate template);
-
-in {
-  options.services.documize = {
-    enable = mkEnableOption "Documize Wiki";
-
-    stateDirectoryName = mkOption {
-      type = types.str;
-      default = "documize";
-      description = ''
-        The name of the directory below {file}`/var/lib/private`
-        where documize runs in and stores, for example, backups.
-      '';
-    };
-
-    package = mkPackageOption pkgs "documize-community" { };
-
-    salt = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "3edIYV6c8B28b19fh";
-      description = ''
-        The salt string used to encode JWT tokens, if not set a random value will be generated.
-      '';
-    };
-
-    cert = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      description = ''
-        The {file}`cert.pem` file used for https.
-      '';
-    };
-
-    key = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      description = ''
-        The {file}`key.pem` file used for https.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5001;
-      description = ''
-        The http/https port number.
-      '';
-    };
-
-    forcesslport = mkOption {
-      type = types.nullOr types.port;
-      default = null;
-      description = ''
-        Redirect given http port number to TLS.
-      '';
-    };
-
-    offline = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Set `true` for offline mode.
-      '';
-      apply = v: if true == v then 1 else 0;
-    };
-
-    dbtype = mkOption {
-      type = types.enum [ "mysql" "percona" "mariadb" "postgresql" "sqlserver" ];
-      default = "postgresql";
-      description = ''
-        Specify the database provider: `mysql`, `percona`, `mariadb`, `postgresql`, `sqlserver`
-      '';
-    };
-
-    db = mkOption {
-      type = types.str;
-      description = ''
-        Database specific connection string for example:
-        - MySQL/Percona/MariaDB:
-          `user:password@tcp(host:3306)/documize`
-        - MySQLv8+:
-          `user:password@tcp(host:3306)/documize?allowNativePasswords=true`
-        - PostgreSQL:
-          `host=localhost port=5432 dbname=documize user=admin password=secret sslmode=disable`
-        - MSSQL:
-          `sqlserver://username:password@localhost:1433?database=Documize` or
-          `sqlserver://sa@localhost/SQLExpress?database=Documize`
-      '';
-    };
-
-    location = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      description = ''
-        reserved
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.documize-server = {
-      description = "Documize Wiki";
-      documentation = [ "https://documize.com/" ];
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        ExecStart = concatStringsSep " " [
-          "${cfg.package}/bin/documize"
-          (mkParams false [ "db" "dbtype" "port" ])
-          (mkParams true [ "offline" "location" "forcesslport" "key" "cert" "salt" ])
-        ];
-        Restart = "always";
-        DynamicUser = "yes";
-        StateDirectory = cfg.stateDirectoryName;
-        WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix
deleted file mode 100644
index c5ea809c8d59..000000000000
--- a/nixos/modules/services/web-apps/dokuwiki.nix
+++ /dev/null
@@ -1,519 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  inherit (lib.options) showOption showFiles;
-
-  cfg = config.services.dokuwiki;
-  eachSite = cfg.sites;
-  user = "dokuwiki";
-  webserver = config.services.${cfg.webserver};
-
-  mkPhpIni = generators.toKeyValue {
-    mkKeyValue = generators.mkKeyValueDefault {} " = ";
-  };
-  mkPhpPackage = cfg: cfg.phpPackage.buildEnv {
-    extraConfig = mkPhpIni cfg.phpOptions;
-  };
-
-  dokuwikiAclAuthConfig = hostName: cfg: let
-    inherit (cfg) acl;
-    acl_gen = concatMapStringsSep "\n" (l: "${l.page} \t ${l.actor} \t ${toString l.level}");
-  in pkgs.writeText "acl.auth-${hostName}.php" ''
-    # acl.auth.php
-    # <?php exit()?>
-    #
-    # Access Control Lists
-    #
-    ${if isString acl then acl else acl_gen acl}
-  '';
-
-  mergeConfig = cfg: {
-    useacl = false; # Dokuwiki default
-    savedir = cfg.stateDir;
-  } // cfg.settings;
-
-  writePhpFile = name: text: pkgs.writeTextFile {
-    inherit name;
-    text = "<?php\n${text}";
-    checkPhase = "${pkgs.php81}/bin/php --syntax-check $target";
-  };
-
-  mkPhpValue = v: let
-    isHasAttr = s: isAttrs v && hasAttr s v;
-  in
-    if isString v then escapeShellArg v
-    # NOTE: If any value contains a , (comma) this will not get escaped
-    else if isList v && any lib.strings.isCoercibleToString v then escapeShellArg (concatMapStringsSep "," toString v)
-    else if isInt v then toString v
-    else if isBool v then toString (if v then 1 else 0)
-    else if isHasAttr "_file" then "trim(file_get_contents(${lib.escapeShellArg v._file}))"
-    else if isHasAttr "_raw" then v._raw
-    else abort "The dokuwiki localConf value ${lib.generators.toPretty {} v} can not be encoded."
-  ;
-
-  mkPhpAttrVals = v: flatten (mapAttrsToList mkPhpKeyVal v);
-  mkPhpKeyVal = k: v: let
-    values = if (isAttrs v && (hasAttr "_file" v || hasAttr "_raw" v )) || !isAttrs v then
-      [" = ${mkPhpValue v};"]
-    else
-      mkPhpAttrVals v;
-  in map (e: "[${escapeShellArg k}]${e}") (flatten values);
-
-  dokuwikiLocalConfig = hostName: cfg: let
-    conf_gen = c: map (v: "$conf${v}") (mkPhpAttrVals c);
-  in writePhpFile "local-${hostName}.php" ''
-    ${concatStringsSep "\n" (conf_gen cfg.mergedConfig)}
-  '';
-
-  dokuwikiPluginsLocalConfig = hostName: cfg: let
-    pc = cfg.pluginsConfig;
-    pc_gen = pc: concatStringsSep "\n" (mapAttrsToList (n: v: "$plugins['${n}'] = ${boolToString v};") pc);
-  in writePhpFile "plugins.local-${hostName}.php" ''
-    ${if isString pc then pc else pc_gen pc}
-  '';
-
-
-  pkg = hostName: cfg: cfg.package.combine {
-    inherit (cfg) plugins templates;
-
-    pname = p: "${p.pname}-${hostName}";
-
-    basePackage = cfg.package;
-    localConfig = dokuwikiLocalConfig hostName cfg;
-    pluginsConfig = dokuwikiPluginsLocalConfig hostName cfg;
-    aclConfig = if cfg.settings.useacl && cfg.acl != null then dokuwikiAclAuthConfig hostName cfg else null;
-  };
-
-  aclOpts = { ... }: {
-    options = {
-
-      page = mkOption {
-        type = types.str;
-        description = "Page or namespace to restrict";
-        example = "start";
-      };
-
-      actor = mkOption {
-        type = types.str;
-        description = "User or group to restrict";
-        example = "@external";
-      };
-
-      level = let
-        available = {
-          "none" = 0;
-          "read" = 1;
-          "edit" = 2;
-          "create" = 4;
-          "upload" = 8;
-          "delete" = 16;
-        };
-      in mkOption {
-        type = types.enum ((attrValues available) ++ (attrNames available));
-        apply = x: if isInt x then x else available.${x};
-        description = ''
-          Permission level to restrict the actor(s) to.
-          See <https://www.dokuwiki.org/acl#background_info> for explanation
-        '';
-        example = "read";
-      };
-    };
-  };
-
-  siteOpts = { options, config, lib, name, ... }:
-    {
-
-      options = {
-        enable = mkEnableOption "DokuWiki web application";
-
-        package = mkPackageOption pkgs "dokuwiki" { };
-
-        stateDir = mkOption {
-          type = types.path;
-          default = "/var/lib/dokuwiki/${name}/data";
-          description = "Location of the DokuWiki state directory.";
-        };
-
-        acl = mkOption {
-          type = with types; nullOr (listOf (submodule aclOpts));
-          default = null;
-          example = literalExpression ''
-            [
-              {
-                page = "start";
-                actor = "@external";
-                level = "read";
-              }
-              {
-                page = "*";
-                actor = "@users";
-                level = "upload";
-              }
-            ]
-          '';
-          description = ''
-            Access Control Lists: see <https://www.dokuwiki.org/acl>
-            Mutually exclusive with services.dokuwiki.aclFile
-            Set this to a value other than null to take precedence over aclFile option.
-
-            Warning: Consider using aclFile instead if you do not
-            want to store the ACL in the world-readable Nix store.
-          '';
-        };
-
-        aclFile = mkOption {
-          type = with types; nullOr str;
-          default = if (config.mergedConfig.useacl && config.acl == null) then "/var/lib/dokuwiki/${name}/acl.auth.php" else null;
-          description = ''
-            Location of the dokuwiki acl rules. Mutually exclusive with services.dokuwiki.acl
-            Mutually exclusive with services.dokuwiki.acl which is preferred.
-            Consult documentation <https://www.dokuwiki.org/acl> for further instructions.
-            Example: <https://github.com/splitbrain/dokuwiki/blob/master/conf/acl.auth.php.dist>
-          '';
-          example = "/var/lib/dokuwiki/${name}/acl.auth.php";
-        };
-
-        pluginsConfig = mkOption {
-          type = with types; attrsOf bool;
-          default = {
-            authad = false;
-            authldap = false;
-            authmysql = false;
-            authpgsql = false;
-          };
-          description = ''
-            List of the dokuwiki (un)loaded plugins.
-          '';
-        };
-
-        usersFile = mkOption {
-          type = with types; nullOr str;
-          default = if config.mergedConfig.useacl then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
-          description = ''
-            Location of the dokuwiki users file. List of users. Format:
-
-                login:passwordhash:Real Name:email:groups,comma,separated
-
-            Create passwordHash easily by using:
-
-                mkpasswd -5 password `pwgen 8 1`
-
-            Example: <https://github.com/splitbrain/dokuwiki/blob/master/conf/users.auth.php.dist>
-            '';
-          example = "/var/lib/dokuwiki/${name}/users.auth.php";
-        };
-
-        plugins = mkOption {
-          type = types.listOf types.path;
-          default = [];
-          description = ''
-                List of path(s) to respective plugin(s) which are copied from the 'plugin' directory.
-
-                ::: {.note}
-                These plugins need to be packaged before use, see example.
-                :::
-          '';
-          example = literalExpression ''
-                let
-                  plugin-icalevents = pkgs.stdenv.mkDerivation rec {
-                    name = "icalevents";
-                    version = "2017-06-16";
-                    src = pkgs.fetchzip {
-                      stripRoot = false;
-                      url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/''${version}/dokuwiki-plugin-icalevents-''${version}.zip";
-                      hash = "sha256-IPs4+qgEfe8AAWevbcCM9PnyI0uoyamtWeg4rEb+9Wc=";
-                    };
-                    installPhase = "mkdir -p $out; cp -R * $out/";
-                  };
-                # And then pass this theme to the plugin list like this:
-                in [ plugin-icalevents ]
-          '';
-        };
-
-        templates = mkOption {
-          type = types.listOf types.path;
-          default = [];
-          description = ''
-                List of path(s) to respective template(s) which are copied from the 'tpl' directory.
-
-                ::: {.note}
-                These templates need to be packaged before use, see example.
-                :::
-          '';
-          example = literalExpression ''
-                let
-                  template-bootstrap3 = pkgs.stdenv.mkDerivation rec {
-                  name = "bootstrap3";
-                  version = "2022-07-27";
-                  src = pkgs.fetchFromGitHub {
-                    owner = "giterlizzi";
-                    repo = "dokuwiki-template-bootstrap3";
-                    rev = "v''${version}";
-                    hash = "sha256-B3Yd4lxdwqfCnfmZdp+i/Mzwn/aEuZ0ovagDxuR6lxo=";
-                  };
-                  installPhase = "mkdir -p $out; cp -R * $out/";
-                };
-                # And then pass this theme to the template list like this:
-                in [ template-bootstrap3 ]
-          '';
-        };
-
-        poolConfig = mkOption {
-          type = with types; attrsOf (oneOf [ str int bool ]);
-          default = {
-            "pm" = "dynamic";
-            "pm.max_children" = 32;
-            "pm.start_servers" = 2;
-            "pm.min_spare_servers" = 2;
-            "pm.max_spare_servers" = 4;
-            "pm.max_requests" = 500;
-          };
-          description = ''
-            Options for the DokuWiki PHP pool. See the documentation on `php-fpm.conf`
-            for details on configuration directives.
-          '';
-        };
-
-        phpPackage = mkPackageOption pkgs "php" {
-          default = "php81";
-          example = "php82";
-        };
-
-        phpOptions = mkOption {
-          type = types.attrsOf types.str;
-          default = {};
-          description = ''
-            Options for PHP's php.ini file for this dokuwiki site.
-          '';
-          example = literalExpression ''
-          {
-            "opcache.interned_strings_buffer" = "8";
-            "opcache.max_accelerated_files" = "10000";
-            "opcache.memory_consumption" = "128";
-            "opcache.revalidate_freq" = "15";
-            "opcache.fast_shutdown" = "1";
-          }
-          '';
-        };
-
-        settings = mkOption {
-          type = types.attrsOf types.anything;
-          default = {
-            useacl = true;
-            superuser = "admin";
-          };
-          description = ''
-            Structural DokuWiki configuration.
-            Refer to <https://www.dokuwiki.org/config>
-            for details and supported values.
-            Settings can either be directly set from nix,
-            loaded from a file using `._file` or obtained from any
-            PHP function calls using `._raw`.
-          '';
-          example = literalExpression ''
-            {
-              title = "My Wiki";
-              userewrite = 1;
-              disableactions = [ "register" ]; # Will be concatenated with commas
-              plugin.smtp = {
-                smtp_pass._file = "/var/run/secrets/dokuwiki/smtp_pass";
-                smtp_user._raw = "getenv('DOKUWIKI_SMTP_USER')";
-              };
-            }
-          '';
-        };
-
-        mergedConfig = mkOption {
-          readOnly = true;
-          default = mergeConfig config;
-          defaultText = literalExpression ''
-            {
-              useacl = true;
-            }
-          '';
-          description = ''
-            Read only representation of the final configuration.
-          '';
-        };
-
-    };
-  };
-in
-{
-  options = {
-    services.dokuwiki = {
-
-      sites = mkOption {
-        type = types.attrsOf (types.submodule siteOpts);
-        default = {};
-        description = "Specification of one or more DokuWiki sites to serve";
-      };
-
-      webserver = mkOption {
-        type = types.enum [ "nginx" "caddy" ];
-        default = "nginx";
-        description = ''
-          Whether to use nginx or caddy for virtual host management.
-
-          Further nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
-          See [](#opt-services.nginx.virtualHosts) for further information.
-
-          Further caddy configuration can be done by adapting `services.caddy.virtualHosts.<name>`.
-          See [](#opt-services.caddy.virtualHosts) for further information.
-        '';
-      };
-
-    };
-  };
-
-  # implementation
-  config = mkIf (eachSite != {}) (mkMerge [{
-
-    services.phpfpm.pools = mapAttrs' (hostName: cfg: (
-      nameValuePair "dokuwiki-${hostName}" {
-        inherit user;
-        group = webserver.group;
-
-        phpPackage = mkPhpPackage cfg;
-        phpEnv = optionalAttrs (cfg.usersFile != null) {
-          DOKUWIKI_USERS_AUTH_CONFIG = "${cfg.usersFile}";
-        } // optionalAttrs (cfg.mergedConfig.useacl) {
-          DOKUWIKI_ACL_AUTH_CONFIG = if (cfg.acl != null) then "${dokuwikiAclAuthConfig hostName cfg}" else "${toString cfg.aclFile}";
-        };
-
-        settings = {
-          "listen.owner" = webserver.user;
-          "listen.group" = webserver.group;
-        } // cfg.poolConfig;
-      }
-    )) eachSite;
-
-  }
-
-  {
-    systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
-      "d ${cfg.stateDir}/attic 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/cache 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/index 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/locks 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/log 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/media 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/media_attic 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/media_meta 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/meta 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/pages 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/tmp 0750 ${user} ${webserver.group} - -"
-    ] ++ lib.optional (cfg.aclFile != null) "C ${cfg.aclFile} 0640 ${user} ${webserver.group} - ${pkg hostName cfg}/share/dokuwiki/conf/acl.auth.php.dist"
-    ++ lib.optional (cfg.usersFile != null) "C ${cfg.usersFile} 0640 ${user} ${webserver.group} - ${pkg hostName cfg}/share/dokuwiki/conf/users.auth.php.dist"
-    ) eachSite);
-
-    users.users.${user} = {
-      group = webserver.group;
-      isSystemUser = true;
-    };
-  }
-
-  (mkIf (cfg.webserver == "nginx") {
-    services.nginx = {
-      enable = true;
-      virtualHosts = mapAttrs (hostName: cfg: {
-        serverName = mkDefault hostName;
-        root = "${pkg hostName cfg}/share/dokuwiki";
-
-        locations = {
-          "~ /(conf/|bin/|inc/|install.php)" = {
-            extraConfig = "deny all;";
-          };
-
-          "~ ^/data/" = {
-            root = "${cfg.stateDir}";
-            extraConfig = "internal;";
-          };
-
-          "~ ^/lib.*\.(js|css|gif|png|ico|jpg|jpeg)$" = {
-            extraConfig = "expires 365d;";
-          };
-
-          "/" = {
-            priority = 1;
-            index = "doku.php";
-            extraConfig = ''try_files $uri $uri/ @dokuwiki;'';
-          };
-
-          "@dokuwiki" = {
-            extraConfig = ''
-              # rewrites "doku.php/" out of the URLs if you set the userwrite setting to .htaccess in dokuwiki config page
-              rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last;
-              rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
-              rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
-              rewrite ^/(.*) /doku.php?id=$1&$args last;
-            '';
-          };
-
-          "~ \\.php$" = {
-            extraConfig = ''
-              try_files $uri $uri/ /doku.php;
-              include ${config.services.nginx.package}/conf/fastcgi_params;
-              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-              fastcgi_param REDIRECT_STATUS 200;
-              fastcgi_pass unix:${config.services.phpfpm.pools."dokuwiki-${hostName}".socket};
-              '';
-          };
-
-        };
-      }) eachSite;
-    };
-  })
-
-  (mkIf (cfg.webserver == "caddy") {
-    services.caddy = {
-      enable = true;
-      virtualHosts = mapAttrs' (hostName: cfg: (
-        nameValuePair "http://${hostName}" {
-          extraConfig = ''
-            root * ${pkg hostName cfg}/share/dokuwiki
-            file_server
-
-            encode zstd gzip
-            php_fastcgi unix/${config.services.phpfpm.pools."dokuwiki-${hostName}".socket}
-
-            @restrict_files {
-              path /data/* /conf/* /bin/* /inc/* /vendor/* /install.php
-            }
-
-            respond @restrict_files 404
-
-            @allow_media {
-              path_regexp path ^/_media/(.*)$
-            }
-            rewrite @allow_media /lib/exe/fetch.php?media=/{http.regexp.path.1}
-
-            @allow_detail   {
-              path /_detail*
-            }
-            rewrite @allow_detail /lib/exe/detail.php?media={path}
-
-            @allow_export   {
-              path /_export*
-              path_regexp export /([^/]+)/(.*)
-            }
-            rewrite @allow_export /doku.php?do=export_{http.regexp.export.1}&id={http.regexp.export.2}
-
-            try_files {path} {path}/ /doku.php?id={path}&{query}
-          '';
-        }
-      )) eachSite;
-    };
-  })
-
-  ]);
-
-  meta.maintainers = with maintainers; [
-    _1000101
-    onny
-    dandellion
-    e1mo
-  ];
-}
diff --git a/nixos/modules/services/web-apps/dolibarr.nix b/nixos/modules/services/web-apps/dolibarr.nix
deleted file mode 100644
index 3f9f853e3b25..000000000000
--- a/nixos/modules/services/web-apps/dolibarr.nix
+++ /dev/null
@@ -1,325 +0,0 @@
-{ config, pkgs, lib, ... }:
-let
-  inherit (lib) any boolToString concatStringsSep isBool isString mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption optionalAttrs types mkPackageOption;
-
-  package = cfg.package.override { inherit (cfg) stateDir; };
-
-  cfg = config.services.dolibarr;
-  vhostCfg = lib.optionalAttrs (cfg.nginx != null) config.services.nginx.virtualHosts."${cfg.domain}";
-
-  mkConfigFile = filename: settings:
-    let
-      # hack in special logic for secrets so we read them from a separate file avoiding the nix store
-      secretKeys = [ "force_install_databasepass" "dolibarr_main_db_pass" "dolibarr_main_instance_unique_id" ];
-
-      toStr = k: v:
-        if (any (str: k == str) secretKeys) then v
-        else if isString v then "'${v}'"
-        else if isBool v then boolToString v
-        else if v == null then "null"
-        else toString v
-      ;
-    in
-      pkgs.writeText filename ''
-        <?php
-        ${concatStringsSep "\n" (mapAttrsToList (k: v: "\$${k} = ${toStr k v};") settings)}
-      '';
-
-  # see https://github.com/Dolibarr/dolibarr/blob/develop/htdocs/install/install.forced.sample.php for all possible values
-  install = {
-    force_install_noedit = 2;
-    force_install_main_data_root = "${cfg.stateDir}/documents";
-    force_install_nophpinfo = true;
-    force_install_lockinstall = "444";
-    force_install_distrib = "nixos";
-    force_install_type = "mysqli";
-    force_install_dbserver = cfg.database.host;
-    force_install_port = toString cfg.database.port;
-    force_install_database = cfg.database.name;
-    force_install_databaselogin = cfg.database.user;
-
-    force_install_mainforcehttps = vhostCfg.forceSSL or false;
-    force_install_createuser = false;
-    force_install_dolibarrlogin = null;
-  } // optionalAttrs (cfg.database.passwordFile != null) {
-    force_install_databasepass = ''file_get_contents("${cfg.database.passwordFile}")'';
-  };
-in
-{
-  # interface
-  options.services.dolibarr = {
-    enable = mkEnableOption "dolibarr";
-
-    package = mkPackageOption pkgs "dolibarr" { };
-
-    domain = mkOption {
-      type = types.str;
-      default = "localhost";
-      description = ''
-        Domain name of your server.
-      '';
-    };
-
-    user = mkOption {
-      type = types.str;
-      default = "dolibarr";
-      description = ''
-        User account under which dolibarr runs.
-
-        ::: {.note}
-        If left as the default value this user will automatically be created
-        on system activation, otherwise you are responsible for
-        ensuring the user exists before the dolibarr application starts.
-        :::
-      '';
-    };
-
-    group = mkOption {
-      type = types.str;
-      default = "dolibarr";
-      description = ''
-        Group account under which dolibarr runs.
-
-        ::: {.note}
-        If left as the default value this group will automatically be created
-        on system activation, otherwise you are responsible for
-        ensuring the group exists before the dolibarr application starts.
-        :::
-      '';
-    };
-
-    stateDir = mkOption {
-      type = types.str;
-      default = "/var/lib/dolibarr";
-      description = ''
-        State and configuration directory dolibarr will use.
-      '';
-    };
-
-    database = {
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Database host address.";
-      };
-      port = mkOption {
-        type = types.port;
-        default = 3306;
-        description = "Database host port.";
-      };
-      name = mkOption {
-        type = types.str;
-        default = "dolibarr";
-        description = "Database name.";
-      };
-      user = mkOption {
-        type = types.str;
-        default = "dolibarr";
-        description = "Database username.";
-      };
-      passwordFile = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        example = "/run/keys/dolibarr-dbpassword";
-        description = "Database password file.";
-      };
-      createLocally = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Create the database and database user locally.";
-      };
-    };
-
-    settings = mkOption {
-      type = with types; (attrsOf (oneOf [ bool int str ]));
-      default = { };
-      description = "Dolibarr settings, see <https://github.com/Dolibarr/dolibarr/blob/develop/htdocs/conf/conf.php.example> for details.";
-    };
-
-    nginx = mkOption {
-      type = types.nullOr (types.submodule (
-        lib.recursiveUpdate
-          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
-          {
-            # enable encryption by default,
-            # as sensitive login and Dolibarr (ERP) data should not be transmitted in clear text.
-            options.forceSSL.default = true;
-            options.enableACME.default = true;
-          }
-      ));
-      default = null;
-      example = lib.literalExpression ''
-        {
-          serverAliases = [
-            "dolibarr.''${config.networking.domain}"
-            "erp.''${config.networking.domain}"
-          ];
-          enableACME = false;
-        }
-      '';
-      description = ''
-          With this option, you can customize an nginx virtual host which already has sensible defaults for Dolibarr.
-          Set to {} if you do not need any customization to the virtual host.
-          If enabled, then by default, the {option}`serverName` is
-          `''${domain}`,
-          SSL is active, and certificates are acquired via ACME.
-          If this is set to null (the default), no nginx virtualHost will be configured.
-      '';
-    };
-
-    poolConfig = mkOption {
-      type = with types; attrsOf (oneOf [ str int bool ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for the Dolibarr PHP pool. See the documentation on [`php-fpm.conf`](https://www.php.net/manual/en/install.fpm.configuration.php)
-        for details on configuration directives.
-      '';
-    };
-  };
-
-  # implementation
-  config = mkIf cfg.enable (mkMerge [
-    {
-
-    assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
-        message = "services.dolibarr.database.user must match services.dolibarr.user if the database is to be automatically provisioned";
-      }
-    ];
-
-    services.dolibarr.settings = {
-      dolibarr_main_url_root = "https://${cfg.domain}";
-      dolibarr_main_document_root = "${package}/htdocs";
-      dolibarr_main_url_root_alt = "/custom";
-      dolibarr_main_data_root = "${cfg.stateDir}/documents";
-
-      dolibarr_main_db_host = cfg.database.host;
-      dolibarr_main_db_port = toString cfg.database.port;
-      dolibarr_main_db_name = cfg.database.name;
-      dolibarr_main_db_prefix = "llx_";
-      dolibarr_main_db_user = cfg.database.user;
-      dolibarr_main_db_pass = mkIf (cfg.database.passwordFile != null) ''
-        file_get_contents("${cfg.database.passwordFile}")
-      '';
-      dolibarr_main_db_type = "mysqli";
-      dolibarr_main_db_character_set = mkDefault "utf8";
-      dolibarr_main_db_collation = mkDefault "utf8_unicode_ci";
-
-      # Authentication settings
-      dolibarr_main_authentication = mkDefault "dolibarr";
-
-      # Security settings
-      dolibarr_main_prod = true;
-      dolibarr_main_force_https = vhostCfg.forceSSL or false;
-      dolibarr_main_restrict_os_commands = "${pkgs.mariadb}/bin/mysqldump, ${pkgs.mariadb}/bin/mysql";
-      dolibarr_nocsrfcheck = false;
-      dolibarr_main_instance_unique_id = ''
-        file_get_contents("${cfg.stateDir}/dolibarr_main_instance_unique_id")
-      '';
-      dolibarr_mailing_limit_sendbyweb = false;
-    };
-
-    systemd.tmpfiles.rules = [
-      "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group}"
-      "d '${cfg.stateDir}/documents' 0750 ${cfg.user} ${cfg.group}"
-      "f '${cfg.stateDir}/conf.php' 0660 ${cfg.user} ${cfg.group}"
-      "L '${cfg.stateDir}/install.forced.php' - ${cfg.user} ${cfg.group} - ${mkConfigFile "install.forced.php" install}"
-    ];
-
-    services.mysql = mkIf cfg.database.createLocally {
-      enable = mkDefault true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        { name = cfg.database.user;
-          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    services.nginx.enable = mkIf (cfg.nginx != null) true;
-    services.nginx.virtualHosts."${cfg.domain}" = mkIf (cfg.nginx != null) (lib.mkMerge [
-      cfg.nginx
-      ({
-        root = lib.mkForce "${package}/htdocs";
-        locations."/".index = "index.php";
-        locations."~ [^/]\\.php(/|$)" = {
-          extraConfig = ''
-            fastcgi_split_path_info ^(.+?\.php)(/.*)$;
-            fastcgi_pass unix:${config.services.phpfpm.pools.dolibarr.socket};
-          '';
-        };
-      })
-    ]);
-
-    systemd.services."phpfpm-dolibarr".after = mkIf cfg.database.createLocally [ "mysql.service" ];
-    services.phpfpm.pools.dolibarr = {
-      inherit (cfg) user group;
-      phpPackage = pkgs.php.buildEnv {
-        extensions = { enabled, all }: enabled ++ [ all.calendar ];
-        # recommended by dolibarr web application
-        extraConfig = ''
-          session.use_strict_mode = 1
-          session.cookie_samesite = "Lax"
-          ; open_basedir = "${package}/htdocs, ${cfg.stateDir}"
-          allow_url_fopen = 0
-          disable_functions = "pcntl_alarm, pcntl_fork, pcntl_waitpid, pcntl_wait, pcntl_wifexited, pcntl_wifstopped, pcntl_wifsignaled, pcntl_wifcontinued, pcntl_wexitstatus, pcntl_wtermsig, pcntl_wstopsig, pcntl_signal, pcntl_signal_get_handler, pcntl_signal_dispatch, pcntl_get_last_error, pcntl_strerror, pcntl_sigprocmask, pcntl_sigwaitinfo, pcntl_sigtimedwait, pcntl_exec, pcntl_getpriority, pcntl_setpriority, pcntl_async_signals"
-        '';
-      };
-
-      settings = {
-        "listen.mode" = "0660";
-        "listen.owner" = cfg.user;
-        "listen.group" = cfg.group;
-      } // cfg.poolConfig;
-    };
-
-    # there are several challenges with dolibarr and NixOS which we can address here
-    # - the dolibarr installer cannot be entirely automated, though it can partially be by including a file called install.forced.php
-    # - the dolibarr installer requires write access to its config file during installation, though not afterwards
-    # - the dolibarr config file generally holds secrets generated by the installer, though the config file is a php file so we can read and write these secrets from an external file
-    systemd.services.dolibarr-config = {
-      description = "dolibarr configuration file management via NixOS";
-      wantedBy = [ "multi-user.target" ];
-
-      script = ''
-        # extract the 'main instance unique id' secret that the dolibarr installer generated for us, store it in a file for use by our own NixOS generated configuration file
-        ${pkgs.php}/bin/php -r "include '${cfg.stateDir}/conf.php'; file_put_contents('${cfg.stateDir}/dolibarr_main_instance_unique_id', \$dolibarr_main_instance_unique_id);"
-
-        # replace configuration file generated by installer with the NixOS generated configuration file
-        install -m 644 ${mkConfigFile "conf.php" cfg.settings} '${cfg.stateDir}/conf.php'
-      '';
-
-      serviceConfig = {
-        Type = "oneshot";
-        User = cfg.user;
-        Group = cfg.group;
-        RemainAfterExit = "yes";
-      };
-
-      unitConfig = {
-        ConditionFileNotEmpty = "${cfg.stateDir}/conf.php";
-      };
-    };
-
-    users.users.dolibarr = mkIf (cfg.user == "dolibarr" ) {
-      isSystemUser = true;
-      group = cfg.group;
-    };
-
-    users.groups = optionalAttrs (cfg.group == "dolibarr") {
-      dolibarr = { };
-    };
-  }
-  (mkIf (cfg.nginx != null) {
-    users.users."${config.services.nginx.group}".extraGroups = mkIf (cfg.nginx != null) [ cfg.group ];
-  })
-]);
-}
diff --git a/nixos/modules/services/web-apps/engelsystem.nix b/nixos/modules/services/web-apps/engelsystem.nix
deleted file mode 100644
index fe815f0a9742..000000000000
--- a/nixos/modules/services/web-apps/engelsystem.nix
+++ /dev/null
@@ -1,181 +0,0 @@
-{ config, lib, pkgs, utils, ... }:
-
-let
-  inherit (lib) mkDefault mkEnableOption mkIf mkOption types mkPackageOption;
-  cfg = config.services.engelsystem;
-in {
-  options = {
-    services.engelsystem = {
-      enable = mkOption {
-        default = false;
-        example = true;
-        description = ''
-          Whether to enable engelsystem, an online tool for coordinating volunteers
-          and shifts on large events.
-        '';
-        type = lib.types.bool;
-      };
-
-      domain = mkOption {
-        type = types.str;
-        example = "engelsystem.example.com";
-        description = "Domain to serve on.";
-      };
-
-      package = mkPackageOption pkgs "engelsystem" { };
-
-      createDatabase = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether to create a local database automatically.
-          This will override every database setting in {option}`services.engelsystem.config`.
-        '';
-      };
-    };
-
-    services.engelsystem.config = mkOption {
-      type = types.attrs;
-      default = {
-        database = {
-          host = "localhost";
-          database = "engelsystem";
-          username = "engelsystem";
-        };
-      };
-      example = {
-        maintenance = false;
-        database = {
-          host = "database.example.com";
-          database = "engelsystem";
-          username = "engelsystem";
-          password._secret = "/var/keys/engelsystem/database";
-        };
-        email = {
-          driver = "smtp";
-          host = "smtp.example.com";
-          port = 587;
-          from.address = "engelsystem@example.com";
-          from.name = "example engelsystem";
-          encryption = "tls";
-          username = "engelsystem@example.com";
-          password._secret = "/var/keys/engelsystem/mail";
-        };
-        autoarrive = true;
-        min_password_length = 6;
-        default_locale = "de_DE";
-      };
-      description = ''
-        Options to be added to config.php, as a nix attribute set. Options containing secret data
-        should be set to an attribute set containing the attribute _secret - a string pointing to a
-        file containing the value the option should be set to. See the example to get a better
-        picture of this: in the resulting config.php file, the email.password key will be set to
-        the contents of the /var/keys/engelsystem/mail file.
-
-        See https://engelsystem.de/doc/admin/configuration/ for available options.
-
-        Note that the admin user login credentials cannot be set here - they always default to
-        admin:asdfasdf. Log in and change them immediately.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    # create database
-    services.mysql = mkIf cfg.createDatabase {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureUsers = [{
-        name = "engelsystem";
-        ensurePermissions = { "engelsystem.*" = "ALL PRIVILEGES"; };
-      }];
-      ensureDatabases = [ "engelsystem" ];
-    };
-
-    environment.etc."engelsystem/config.php".source =
-      pkgs.writeText "config.php" ''
-        <?php
-        return json_decode(file_get_contents("/var/lib/engelsystem/config.json"), true);
-      '';
-
-    services.phpfpm.pools.engelsystem = {
-      user = "engelsystem";
-      settings = {
-        "listen.owner" = config.services.nginx.user;
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.max_requests" = 500;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 5;
-        "php_admin_value[error_log]" = "stderr";
-        "php_admin_flag[log_errors]" = true;
-        "catch_workers_output" = true;
-      };
-    };
-
-    services.nginx = {
-      enable = true;
-      virtualHosts."${cfg.domain}".locations = {
-        "/" = {
-          root = "${cfg.package}/share/engelsystem/public";
-          extraConfig = ''
-            index index.php;
-            try_files $uri $uri/ /index.php?$args;
-            autoindex off;
-          '';
-        };
-        "~ \\.php$" = {
-          root = "${cfg.package}/share/engelsystem/public";
-          extraConfig = ''
-            fastcgi_pass unix:${config.services.phpfpm.pools.engelsystem.socket};
-            fastcgi_index index.php;
-            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-            include ${config.services.nginx.package}/conf/fastcgi_params;
-            include ${config.services.nginx.package}/conf/fastcgi.conf;
-          '';
-        };
-      };
-    };
-
-    systemd.services."engelsystem-init" = {
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = { Type = "oneshot"; };
-      script =
-        let
-          genConfigScript = pkgs.writeScript "engelsystem-gen-config.sh"
-            (utils.genJqSecretsReplacementSnippet cfg.config "config.json");
-        in ''
-          umask 077
-          mkdir -p /var/lib/engelsystem/storage/app
-          mkdir -p /var/lib/engelsystem/storage/cache/views
-          cd /var/lib/engelsystem
-          ${genConfigScript}
-          chmod 400 config.json
-          chown -R engelsystem .
-      '';
-    };
-    systemd.services."engelsystem-migrate" = {
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        Type = "oneshot";
-        User = "engelsystem";
-        Group = "engelsystem";
-      };
-      script = ''
-        ${cfg.package}/bin/migrate
-      '';
-      after = [ "engelsystem-init.service" "mysql.service" ];
-    };
-    systemd.services."phpfpm-engelsystem".after =
-      [ "engelsystem-migrate.service" ];
-
-    users.users.engelsystem = {
-      isSystemUser = true;
-      createHome = true;
-      home = "/var/lib/engelsystem/storage";
-      group = "engelsystem";
-    };
-    users.groups.engelsystem = { };
-  };
-}
diff --git a/nixos/modules/services/web-apps/ethercalc.nix b/nixos/modules/services/web-apps/ethercalc.nix
deleted file mode 100644
index fd6c6e05a872..000000000000
--- a/nixos/modules/services/web-apps/ethercalc.nix
+++ /dev/null
@@ -1,57 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.ethercalc;
-in {
-  options = {
-    services.ethercalc = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          ethercalc, an online collaborative spreadsheet server.
-
-          Persistent state will be maintained under
-          {file}`/var/lib/ethercalc`. Upstream supports using a
-          redis server for storage and recommends the redis backend for
-          intensive use; however, the Nix module doesn't currently support
-          redis.
-
-          Note that while ethercalc is a good and robust project with an active
-          issue tracker, there haven't been new commits since the end of 2020.
-        '';
-      };
-
-      package = mkPackageOption pkgs "ethercalc" { };
-
-      host = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
-        description = "Address to listen on (use 0.0.0.0 to allow access from any address).";
-      };
-
-      port = mkOption {
-        type = types.port;
-        default = 8000;
-        description = "Port to bind to.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.ethercalc = {
-      description = "Ethercalc service";
-      wantedBy    = [ "multi-user.target" ];
-      after       = [ "network.target" ];
-      serviceConfig = {
-        DynamicUser    =   true;
-        ExecStart        = "${cfg.package}/bin/ethercalc --host ${cfg.host} --port ${toString cfg.port}";
-        Restart          = "always";
-        StateDirectory   = "ethercalc";
-        WorkingDirectory = "/var/lib/ethercalc";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/filesender.md b/nixos/modules/services/web-apps/filesender.md
deleted file mode 100644
index 44d066761b9a..000000000000
--- a/nixos/modules/services/web-apps/filesender.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# FileSender {#module-services-filesender}
-
-[FileSender](https://filesender.org/software/) is a software that makes it easy to send and receive big files.
-
-## Quickstart {#module-services-filesender-quickstart}
-
-FileSender uses [SimpleSAMLphp](https://simplesamlphp.org/) for authentication, which needs to be configured separately.
-
-Minimal working instance of FileSender that uses password-authentication would look like this:
-
-```nix
-{
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-  services.filesender = {
-    enable = true;
-    localDomain = "filesender.example.com";
-    configureNginx = true;
-    database.createLocally = true;
-
-    settings = {
-      auth_sp_saml_authentication_source = "default";
-      auth_sp_saml_uid_attribute = "uid";
-      storage_filesystem_path = "<STORAGE PATH FOR UPLOADED FILES>";
-      admin = "admin";
-      admin_email = "admin@example.com";
-      email_reply_to = "noreply@example.com";
-    };
-  };
-  services.simplesamlphp.filesender = {
-    settings = {
-      "module.enable".exampleauth = true;
-    };
-    authSources = {
-      admin = [ "core:AdminPassword" ];
-      default = format.lib.mkMixedArray [ "exampleauth:UserPass" ] {
-        "admin:admin123" = {
-          uid = [ "admin" ];
-          cn = [ "admin" ];
-          mail = [ "admin@example.com" ];
-        };
-      };
-    };
-  };
-}
-```
-
-::: {.warning}
-Example above uses hardcoded clear-text password, in production you should use other authentication method like LDAP. You can check supported authentication methods [in SimpleSAMLphp documentation](https://simplesamlphp.org/docs/stable/simplesamlphp-idp.html).
-:::
diff --git a/nixos/modules/services/web-apps/filesender.nix b/nixos/modules/services/web-apps/filesender.nix
deleted file mode 100644
index bc8d465643f2..000000000000
--- a/nixos/modules/services/web-apps/filesender.nix
+++ /dev/null
@@ -1,253 +0,0 @@
-{
-  config,
-  lib,
-  pkgs,
-  ...
-}:
-let
-  format = pkgs.formats.php { finalVariable = "config"; };
-
-  cfg = config.services.filesender;
-  simpleSamlCfg = config.services.simplesamlphp.filesender;
-  fpm = config.services.phpfpm.pools.filesender;
-
-  filesenderConfigDirectory = pkgs.runCommand "filesender-config" { } ''
-    mkdir $out
-    cp ${format.generate "config.php" cfg.settings} $out/config.php
-  '';
-in
-{
-  meta = {
-    maintainers = with lib.maintainers; [ nhnn ];
-    doc = ./filesender.md;
-  };
-
-  options.services.filesender = with lib; {
-    enable = mkEnableOption "FileSender";
-    package = mkPackageOption pkgs "filesender" { };
-    user = mkOption {
-      description = "User under which filesender runs.";
-      type = types.str;
-      default = "filesender";
-    };
-    database = {
-      createLocally = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Create the PostgreSQL database and database user locally.
-        '';
-      };
-      hostname = mkOption {
-        type = types.str;
-        default = "/run/postgresql";
-        description = "Database hostname.";
-      };
-      port = mkOption {
-        type = types.port;
-        default = 5432;
-        description = "Database port.";
-      };
-      name = mkOption {
-        type = types.str;
-        default = "filesender";
-        description = "Database name.";
-      };
-      user = mkOption {
-        type = types.str;
-        default = "filesender";
-        description = "Database user.";
-      };
-      passwordFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        example = "/run/keys/filesender-dbpassword";
-        description = ''
-          A file containing the password corresponding to
-          [](#opt-services.filesender.database.user).
-        '';
-      };
-    };
-    settings = mkOption {
-      type = types.submodule {
-        freeformType = format.type;
-        options = {
-          site_url = mkOption {
-            type = types.str;
-            description = "Site URL. Used in emails, to build URLs for logging in, logging out, build URL for upload endpoint for web workers, to include scripts etc.";
-          };
-          admin = mkOption {
-            type = types.commas;
-            description = ''
-              UIDs (as per the configured saml_uid_attribute) of FileSender administrators.
-              Accounts with these UIDs can access the Admin page through the web UI.
-            '';
-          };
-          admin_email = mkOption {
-            type = types.commas;
-            description = ''
-              Email address of FileSender administrator(s).
-              Emails regarding disk full etc. are sent here.
-              You should use a role-address here.
-            '';
-          };
-          storage_filesystem_path = mkOption {
-            type = types.nullOr types.str;
-            description = "When using storage type filesystem this is the absolute path to the file system where uploaded files are stored until they expire. Your FileSender storage root.";
-          };
-          log_facilities = mkOption {
-            type = format.type;
-            default = [ { type = "error_log"; } ];
-            description = "Defines where FileSender logging is sent. You can sent logging to a file, to syslog or to the default PHP log facility (as configured through your webserver's PHP module). The directive takes an array of one or more logging targets. Logging can be sent to multiple targets simultaneously. Each logging target is a list containing the name of the logging target and a number of attributes which vary per log target. See below for the exact definiation of each log target.";
-          };
-        };
-      };
-      default = { };
-      description = ''
-        Configuration options used by FileSender.
-        See [](https://docs.filesender.org/filesender/v2.0/admin/configuration/)
-        for available options.
-      '';
-    };
-    configureNginx = mkOption {
-      type = types.bool;
-      default = true;
-      description = "Configure nginx as a reverse proxy for FileSender.";
-    };
-    localDomain = mkOption {
-      type = types.str;
-      example = "filesender.example.org";
-      description = "The domain serving your FileSender instance.";
-    };
-    poolSettings = mkOption {
-      type =
-        with types;
-        attrsOf (oneOf [
-          str
-          int
-          bool
-        ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = "32";
-        "pm.start_servers" = "2";
-        "pm.min_spare_servers" = "2";
-        "pm.max_spare_servers" = "4";
-        "pm.max_requests" = "500";
-      };
-      description = ''
-        Options for FileSender's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
-      '';
-    };
-  };
-  config = lib.mkIf cfg.enable {
-    services.simplesamlphp.filesender = {
-      phpfpmPool = "filesender";
-      localDomain = cfg.localDomain;
-      settings.baseurlpath = lib.mkDefault "https://${cfg.localDomain}/saml";
-    };
-
-    services.phpfpm = {
-      pools.filesender = {
-        user = cfg.user;
-        group = config.services.nginx.group;
-        phpEnv = {
-          FILESENDER_CONFIG_DIR = toString filesenderConfigDirectory;
-          SIMPLESAMLPHP_CONFIG_DIR = toString simpleSamlCfg.configDir;
-        };
-        settings = {
-          "listen.owner" = config.services.nginx.user;
-          "listen.group" = config.services.nginx.group;
-        } // cfg.poolSettings;
-      };
-    };
-
-    services.nginx = lib.mkIf cfg.configureNginx {
-      enable = true;
-      virtualHosts.${cfg.localDomain} = {
-        root = "${cfg.package}/www";
-        extraConfig = ''
-          index index.php;
-        '';
-        locations = {
-          "/".extraConfig = ''
-            try_files $uri $uri/ /index.php?args;
-          '';
-          "~ [^/]\\.php(/|$)" = {
-            extraConfig = ''
-              fastcgi_split_path_info  ^(.+\.php)(/.+)$;
-              fastcgi_pass  unix:${fpm.socket};
-              include ${pkgs.nginx}/conf/fastcgi.conf;
-              fastcgi_intercept_errors on;
-              fastcgi_param PATH_INFO       $fastcgi_path_info;
-              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-            '';
-          };
-          "~ /\\.".extraConfig = "deny all;";
-        };
-      };
-    };
-
-    services.postgresql = lib.mkIf cfg.database.createLocally {
-      enable = true;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        {
-          name = cfg.database.user;
-          ensureDBOwnership = true;
-        }
-      ];
-    };
-
-    services.filesender.settings = lib.mkMerge [
-      (lib.mkIf cfg.database.createLocally {
-        db_host = "/run/postgresql";
-        db_port = "5432";
-        db_password = "."; # FileSender requires it even when on UNIX socket auth.
-      })
-      (lib.mkIf (!cfg.database.createLocally) {
-        db_host = cfg.database.hostname;
-        db_port = toString cfg.database.port;
-        db_password = format.lib.mkRaw "file_get_contents('${cfg.database.passwordFile}')";
-      })
-      {
-        site_url = lib.mkDefault "https://${cfg.localDomain}";
-        db_type = "pgsql";
-        db_username = cfg.database.user;
-        db_database = cfg.database.name;
-        "auth_sp_saml_simplesamlphp_url" = "/saml";
-        "auth_sp_saml_simplesamlphp_location" = "${simpleSamlCfg.libDir}";
-      }
-    ];
-
-    systemd.services.filesender-initdb = {
-      description = "Init filesender DB";
-
-      wantedBy = [
-        "multi-user.target"
-        "phpfpm-filesender.service"
-      ];
-      after = [ "postgresql.service" ];
-
-      restartIfChanged = true;
-
-      serviceConfig = {
-        Environment = [
-          "FILESENDER_CONFIG_DIR=${toString filesenderConfigDirectory}"
-          "SIMPLESAMLPHP_CONFIG_DIR=${toString simpleSamlCfg.configDir}"
-        ];
-        Type = "oneshot";
-        Group = config.services.nginx.group;
-        User = "filesender";
-        ExecStart = "${fpm.phpPackage}/bin/php ${cfg.package}/scripts/upgrade/database.php";
-      };
-    };
-
-    users.extraUsers.filesender = lib.mkIf (cfg.user == "filesender") {
-      home = "/var/lib/filesender";
-      group = config.services.nginx.group;
-      createHome = true;
-      isSystemUser = true;
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/firefly-iii.nix b/nixos/modules/services/web-apps/firefly-iii.nix
deleted file mode 100644
index 3e51bd226b02..000000000000
--- a/nixos/modules/services/web-apps/firefly-iii.nix
+++ /dev/null
@@ -1,381 +0,0 @@
-{ pkgs, config, lib, ... }:
-
-let
-  inherit (lib) optionalString mkDefault mkIf mkOption mkEnableOption literalExpression;
-  inherit (lib.types) nullOr attrsOf oneOf str int bool path package enum submodule;
-  inherit (lib.strings) concatLines removePrefix toShellVars removeSuffix hasSuffix;
-  inherit (lib.attrsets) mapAttrsToList attrValues genAttrs filterAttrs mapAttrs' nameValuePair;
-  inherit (builtins) isInt isString toString typeOf;
-
-  cfg = config.services.firefly-iii;
-
-  user = cfg.user;
-  group = cfg.group;
-
-  defaultUser = "firefly-iii";
-  defaultGroup = "firefly-iii";
-
-  artisan = "${cfg.package}/artisan";
-
-  env-file-values = mapAttrs' (n: v: nameValuePair (removeSuffix "_FILE" n) v)
-    (filterAttrs (n: v: hasSuffix "_FILE" n) cfg.settings);
-  env-nonfile-values = filterAttrs (n: v: ! hasSuffix "_FILE" n) cfg.settings;
-
-  fileenv-func = ''
-    set -a
-    ${toShellVars env-nonfile-values}
-    ${concatLines (mapAttrsToList (n: v: "${n}=\"$(< ${v})\"") env-file-values)}
-    set +a
-  '';
-
-  firefly-iii-maintenance = pkgs.writeShellScript "firefly-iii-maintenance.sh" ''
-    ${fileenv-func}
-
-    ${optionalString (cfg.settings.DB_CONNECTION == "sqlite")
-      "touch ${cfg.dataDir}/storage/database/database.sqlite"}
-    ${artisan} package:discover
-    ${artisan} firefly-iii:upgrade-database
-    ${artisan} firefly-iii:laravel-passport-keys
-    ${artisan} cache:clear
-    ${artisan} view:cache
-    ${artisan} route:cache
-    ${artisan} config:cache
-  '';
-
-  commonServiceConfig = {
-    Type = "oneshot";
-    User = user;
-    Group = group;
-    StateDirectory = "firefly-iii";
-    ReadWritePaths = [cfg.dataDir];
-    WorkingDirectory = cfg.package;
-    PrivateTmp = true;
-    PrivateDevices = true;
-    CapabilityBoundingSet = "";
-    AmbientCapabilities = "";
-    ProtectSystem = "strict";
-    ProtectKernelTunables = true;
-    ProtectKernelModules = true;
-    ProtectControlGroups = true;
-    ProtectClock = true;
-    ProtectHostname = true;
-    ProtectHome = "tmpfs";
-    ProtectKernelLogs = true;
-    ProtectProc = "invisible";
-    ProcSubset = "pid";
-    PrivateNetwork = false;
-    RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
-    SystemCallArchitectures = "native";
-    SystemCallFilter = [
-      "@system-service @resources"
-      "~@obsolete @privileged"
-    ];
-    RestrictSUIDSGID = true;
-    RemoveIPC = true;
-    NoNewPrivileges = true;
-    RestrictRealtime = true;
-    RestrictNamespaces = true;
-    LockPersonality = true;
-    PrivateUsers = true;
-  };
-
-in {
-
-  options.services.firefly-iii = {
-
-    enable = mkEnableOption "Firefly III: A free and open source personal finance manager";
-
-    user = mkOption {
-      type = str;
-      default = defaultUser;
-      description = "User account under which firefly-iii runs.";
-    };
-
-    group = mkOption {
-      type = str;
-      default = if cfg.enableNginx then "nginx" else defaultGroup;
-      defaultText = "If `services.firefly-iii.enableNginx` is true then `nginx` else ${defaultGroup}";
-      description = ''
-        Group under which firefly-iii runs. It is best to set this to the group
-        of whatever webserver is being used as the frontend.
-      '';
-    };
-
-    dataDir = mkOption {
-      type = path;
-      default = "/var/lib/firefly-iii";
-      description = ''
-        The place where firefly-iii stores its state.
-      '';
-    };
-
-    package = mkOption {
-      type = package;
-      default = pkgs.firefly-iii;
-      defaultText = literalExpression "pkgs.firefly-iii";
-      description = ''
-        The firefly-iii package served by php-fpm and the webserver of choice.
-        This option can be used to point the webserver to the correct root. It
-        may also be used to set the package to a different version, say a
-        development version.
-      '';
-      apply = firefly-iii : firefly-iii.override (prev: {
-        dataDir = cfg.dataDir;
-      });
-    };
-
-    enableNginx = mkOption {
-      type = bool;
-      default = false;
-      description = ''
-        Whether to enable nginx or not. If enabled, an nginx virtual host will
-        be created for access to firefly-iii. If not enabled, then you may use
-        `''${config.services.firefly-iii.package}` as your document root in
-        whichever webserver you wish to setup.
-      '';
-    };
-
-    virtualHost = mkOption {
-      type = str;
-      default = "localhost";
-      description = ''
-        The hostname at which you wish firefly-iii to be served. If you have
-        enabled nginx using `services.firefly-iii.enableNginx` then this will
-        be used.
-      '';
-    };
-
-    poolConfig = mkOption {
-      type = attrsOf (oneOf [ str int bool ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for the Firefly III PHP pool. See the documentation on <literal>php-fpm.conf</literal>
-        for details on configuration directives.
-      '';
-    };
-
-    settings = mkOption {
-      default = {};
-      description = ''
-        Options for firefly-iii configuration. Refer to
-        <https://github.com/firefly-iii/firefly-iii/blob/main/.env.example> for
-        details on supported values. All <option>_FILE values supported by
-        upstream are supported here.
-
-        APP_URL will be the same as `services.firefly-iii.virtualHost` if the
-        former is unset in `services.firefly-iii.settings`.
-      '';
-      example = literalExpression ''
-        {
-          APP_ENV = "production";
-          APP_KEY_FILE = "/var/secrets/firefly-iii-app-key.txt";
-          SITE_OWNER = "mail@example.com";
-          DB_CONNECTION = "mysql";
-          DB_HOST = "db";
-          DB_PORT = 3306;
-          DB_DATABASE = "firefly";
-          DB_USERNAME = "firefly";
-          DB_PASSWORD_FILE = "/var/secrets/firefly-iii-mysql-password.txt;
-        }
-      '';
-      type = submodule {
-        freeformType = attrsOf (oneOf [str int bool]);
-        options = {
-          DB_CONNECTION = mkOption {
-            type = enum [ "sqlite" "pgsql" "mysql" ];
-            default = "sqlite";
-            example = "pgsql";
-            description = ''
-              The type of database you wish to use. Can be one of "sqlite",
-              "mysql" or "pgsql".
-            '';
-          };
-          APP_ENV = mkOption {
-            type = enum [ "local" "production" "testing" ];
-            default = "local";
-            example = "production";
-            description = ''
-              The app environment. It is recommended to keep this at "local".
-              Possible values are "local", "production" and "testing"
-            '';
-          };
-          DB_PORT = mkOption {
-            type = nullOr int;
-            default = if cfg.settings.DB_CONNECTION == "pgsql" then 5432
-                      else if cfg.settings.DB_CONNECTION == "mysql" then 3306
-                      else null;
-            defaultText = ''
-              `null` if DB_CONNECTION is "sqlite", `3306` if "mysql", `5432` if "pgsql"
-            '';
-            description = ''
-              The port your database is listening at. sqlite does not require
-              this value to be filled.
-            '';
-          };
-          DB_HOST = mkOption {
-            type = str;
-            default = if cfg.settings.DB_CONNECTION == "pgsql" then "/run/postgresql"
-                      else "localhost";
-            defaultText = ''
-              "localhost" if DB_CONNECTION is "sqlite" or "mysql", "/run/postgresql" if "pgsql".
-            '';
-            description = ''
-              The machine which hosts your database. This is left at the
-              default value for "mysql" because we use the "DB_SOCKET" option
-              to connect to a unix socket instead. "pgsql" requires that the
-              unix socket location be specified here instead of at "DB_SOCKET".
-              This option does not affect "sqlite".
-            '';
-          };
-          APP_KEY_FILE = mkOption {
-            type = path;
-            description = ''
-              The path to your appkey. The file should contain a 32 character
-              random app key. This may be set using `echo "base64:$(head -c 32
-              /dev/urandom | base64)" > /path/to/key-file`.
-            '';
-          };
-          APP_URL = mkOption {
-            type = str;
-            default = if cfg.virtualHost == "localhost" then "http://${cfg.virtualHost}"
-                      else "https://${cfg.virtualHost}";
-            defaultText = ''
-              http(s)://''${config.services.firefly-iii.virtualHost}
-            '';
-            description = ''
-              The APP_URL used by firefly-iii internally. Please make sure this
-              URL matches the external URL of your Firefly III installation. It
-              is used to validate specific requests and to generate URLs in
-              emails.
-            '';
-          };
-        };
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    services.phpfpm.pools.firefly-iii = {
-      inherit user group;
-      phpPackage = cfg.package.phpPackage;
-      phpOptions = ''
-        log_errors = on
-      '';
-      settings = {
-        "listen.mode" = "0660";
-        "listen.owner" = user;
-        "listen.group" = group;
-        "clear_env" = "no";
-      } // cfg.poolConfig;
-    };
-
-    systemd.services.firefly-iii-setup = {
-      after = [ "postgresql.service" "mysql.service" ];
-      requiredBy = [ "phpfpm-firefly-iii.service" ];
-      before = [ "phpfpm-firefly-iii.service" ];
-      serviceConfig = {
-        ExecStart = firefly-iii-maintenance;
-        RuntimeDirectory = "phpfpm";
-        RuntimeDirectoryPreserve = true;
-        RemainAfterExit = true;
-      } // commonServiceConfig;
-      unitConfig.JoinsNamespaceOf = "phpfpm-firefly-iii.service";
-      restartTriggers = [ cfg.package ];
-    };
-
-    systemd.services.firefly-iii-cron = {
-      after = [ "firefly-iii-setup.service" "postgresql.service" "mysql.service" ];
-      wants = [ "firefly-iii-setup.service" ];
-      description = "Daily Firefly III cron job";
-      serviceConfig = {
-        ExecStart = "${artisan} firefly-iii:cron";
-      } // commonServiceConfig;
-    };
-
-    systemd.timers.firefly-iii-cron = {
-      description = "Trigger Firefly Cron";
-      timerConfig = {
-        OnCalendar = "Daily";
-        RandomizedDelaySec = "1800s";
-        Persistent = true;
-      };
-      wantedBy = [ "timers.target" ];
-      restartTriggers = [ cfg.package ];
-    };
-
-    services.nginx = mkIf cfg.enableNginx {
-      enable = true;
-      recommendedTlsSettings = mkDefault true;
-      recommendedOptimisation = mkDefault true;
-      recommendedGzipSettings = mkDefault true;
-      virtualHosts.${cfg.virtualHost} = {
-        root = "${cfg.package}/public";
-        locations = {
-          "/" = {
-            tryFiles = "$uri $uri/ /index.php?$query_string";
-            index = "index.php";
-            extraConfig = ''
-              sendfile off;
-            '';
-          };
-          "~ \.php$" = {
-            extraConfig = ''
-              include ${config.services.nginx.package}/conf/fastcgi_params ;
-              fastcgi_param SCRIPT_FILENAME $request_filename;
-              fastcgi_param modHeadersAvailable true; #Avoid sending the security headers twice
-              fastcgi_pass unix:${config.services.phpfpm.pools.firefly-iii.socket};
-            '';
-          };
-        };
-      };
-    };
-
-    systemd.tmpfiles.settings."10-firefly-iii" = genAttrs [
-      "${cfg.dataDir}/storage"
-      "${cfg.dataDir}/storage/app"
-      "${cfg.dataDir}/storage/database"
-      "${cfg.dataDir}/storage/export"
-      "${cfg.dataDir}/storage/framework"
-      "${cfg.dataDir}/storage/framework/cache"
-      "${cfg.dataDir}/storage/framework/sessions"
-      "${cfg.dataDir}/storage/framework/views"
-      "${cfg.dataDir}/storage/logs"
-      "${cfg.dataDir}/storage/upload"
-      "${cfg.dataDir}/cache"
-    ] (n: {
-      d = {
-        group = group;
-        mode = "0700";
-        user = user;
-      };
-    }) // {
-      "${cfg.dataDir}".d = {
-        group = group;
-        mode = "0710";
-        user = user;
-      };
-    };
-
-    users = {
-      users = mkIf (user == defaultUser) {
-        ${defaultUser} = {
-          description = "Firefly-iii service user";
-          inherit group;
-          isSystemUser = true;
-          home = cfg.dataDir;
-        };
-      };
-      groups = mkIf (group == defaultGroup) {
-        ${defaultGroup} = {};
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/flarum.nix b/nixos/modules/services/web-apps/flarum.nix
deleted file mode 100644
index a967c3b121bd..000000000000
--- a/nixos/modules/services/web-apps/flarum.nix
+++ /dev/null
@@ -1,210 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-with lib;
-
-let
-  cfg = config.services.flarum;
-
-  flarumInstallConfig = pkgs.writeText "config.json" (builtins.toJSON {
-    debug = false;
-    offline = false;
-
-    baseUrl = cfg.baseUrl;
-    databaseConfiguration = cfg.database;
-    adminUser = {
-      username = cfg.adminUser;
-      password = cfg.initialAdminPassword;
-      email = cfg.adminEmail;
-    };
-    settings = {
-      forum_title = cfg.forumTitle;
-    };
-  });
-in {
-  options.services.flarum = {
-    enable = mkEnableOption "Flarum discussion platform";
-
-    package = mkPackageOption pkgs "flarum" { };
-
-    forumTitle = mkOption {
-      type = types.str;
-      default = "A Flarum Forum on NixOS";
-      description = "Title of the forum.";
-    };
-
-    domain = mkOption {
-      type = types.str;
-      default = "localhost";
-      example = "forum.example.com";
-      description = "Domain to serve on.";
-    };
-
-    baseUrl = mkOption {
-      type = types.str;
-      default = "http://localhost";
-      example = "https://forum.example.com";
-      description = "Change `domain` instead.";
-    };
-
-    adminUser = mkOption {
-      type = types.str;
-      default = "flarum";
-      description = "Username for first web application administrator";
-    };
-
-    adminEmail = mkOption {
-      type = types.str;
-      default = "admin@example.com";
-      description = "Email for first web application administrator";
-    };
-
-    initialAdminPassword = mkOption {
-      type = types.str;
-      default = "flarum";
-      description = "Initial password for the adminUser";
-    };
-
-    user = mkOption {
-      type = types.str;
-      default = "flarum";
-      description = "System user to run Flarum";
-    };
-
-    group = mkOption {
-      type = types.str;
-      default = "flarum";
-      description = "System group to run Flarum";
-    };
-
-    stateDir = mkOption {
-      type = types.path;
-      default = "/var/lib/flarum";
-      description = "Home directory for writable storage";
-    };
-
-    database = mkOption rec {
-      type = with types; attrsOf (oneOf [str bool int]);
-      description = "MySQL database parameters";
-      default = {
-        # the database driver; i.e. MySQL; MariaDB...
-        driver = "mysql";
-        # the host of the connection; localhost in most cases unless using an external service
-        host = "localhost";
-        # the name of the database in the instance
-        database = "flarum";
-        # database username
-        username = "flarum";
-        # database password
-        password = "";
-        # the prefix for the tables; useful if you are sharing the same database with another service
-        prefix = "";
-        # the port of the connection; defaults to 3306 with MySQL
-        port = 3306;
-        strict = false;
-      };
-    };
-
-    createDatabaseLocally = mkOption {
-      type = types.bool;
-      default = true;
-      description = "Create the database and database user locally, and run installation.";
-    };
-  };
-
-  config = mkIf cfg.enable {
-    users.users.${cfg.user} = {
-      isSystemUser = true;
-      home = cfg.stateDir;
-      createHome = true;
-      group = cfg.group;
-    };
-    users.groups.${cfg.group} = {};
-
-    services.phpfpm.pools.flarum = {
-      user = cfg.user;
-      settings = {
-        "listen.owner" = config.services.nginx.user;
-        "listen.group" = config.services.nginx.group;
-        "listen.mode" = "0600";
-        "pm" = mkDefault "dynamic";
-        "pm.max_children" = mkDefault 10;
-        "pm.max_requests" = mkDefault 500;
-        "pm.start_servers" = mkDefault 2;
-        "pm.min_spare_servers" = mkDefault 1;
-        "pm.max_spare_servers" = mkDefault 3;
-      };
-      phpOptions = ''
-        error_log = syslog
-        log_errors = on
-      '';
-    };
-
-    services.nginx = {
-      enable = true;
-      virtualHosts."${cfg.domain}" = {
-        root = "${cfg.stateDir}/public";
-        locations."~ \.php$".extraConfig = ''
-          fastcgi_pass unix:${config.services.phpfpm.pools.flarum.socket};
-          fastcgi_index site.php;
-        '';
-        extraConfig = ''
-          index index.php;
-          include ${cfg.package}/share/php/flarum/.nginx.conf;
-        '';
-      };
-    };
-
-    services.mysql = mkIf cfg.enable {
-      enable = true;
-      package = pkgs.mysql;
-      ensureDatabases = [cfg.database.database];
-      ensureUsers = [
-        {
-          name = cfg.database.username;
-          ensurePermissions = {
-            "${cfg.database.database}.*" = "ALL PRIVILEGES";
-          };
-        }
-      ];
-    };
-
-    assertions = [
-      {
-        assertion = !cfg.createDatabaseLocally || cfg.database.driver == "mysql";
-        message = "Flarum can only be automatically installed in MySQL/MariaDB.";
-      }
-    ];
-
-    systemd.services.flarum-install = {
-      description = "Flarum installation";
-      requiredBy = ["phpfpm-flarum.service"];
-      before = ["phpfpm-flarum.service"];
-      requires = ["mysql.service"];
-      after = ["mysql.service"];
-      serviceConfig = {
-        Type = "oneshot";
-        User = cfg.user;
-        Group = cfg.group;
-      };
-      path = [config.services.phpfpm.phpPackage];
-      script = ''
-        mkdir -p ${cfg.stateDir}/{extensions,public/assets/avatars}
-        mkdir -p ${cfg.stateDir}/storage/{cache,formatter,sessions,views}
-        cd ${cfg.stateDir}
-        cp -f ${cfg.package}/share/php/flarum/{extend.php,site.php,flarum} .
-        ln -sf ${cfg.package}/share/php/flarum/vendor .
-        ln -sf ${cfg.package}/share/php/flarum/public/index.php public/
-        chmod a+x . public
-        chmod +x site.php extend.php flarum
-      '' + optionalString (cfg.createDatabaseLocally && cfg.database.driver == "mysql") ''
-        if [ ! -f config.php ]; then
-            php flarum install --file=${flarumInstallConfig}
-        fi
-        php flarum migrate
-        php flarum cache:clear
-      '';
-    };
-  };
-
-  meta.maintainers = with lib.maintainers; [ fsagbuya jasonodoom ];
-}
diff --git a/nixos/modules/services/web-apps/fluidd.nix b/nixos/modules/services/web-apps/fluidd.nix
deleted file mode 100644
index f30127dd17ad..000000000000
--- a/nixos/modules/services/web-apps/fluidd.nix
+++ /dev/null
@@ -1,61 +0,0 @@
-{ config, lib, pkgs, ... }:
-with lib;
-let
-  cfg = config.services.fluidd;
-  moonraker = config.services.moonraker;
-in
-{
-  options.services.fluidd = {
-    enable = mkEnableOption "Fluidd, a Klipper web interface for managing your 3d printer";
-
-    package = mkPackageOption pkgs "fluidd" { };
-
-    hostName = mkOption {
-      type = types.str;
-      default = "localhost";
-      description = "Hostname to serve fluidd on";
-    };
-
-    nginx = mkOption {
-      type = types.submodule
-        (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
-      default = { };
-      example = literalExpression ''
-        {
-          serverAliases = [ "fluidd.''${config.networking.domain}" ];
-        }
-      '';
-      description = "Extra configuration for the nginx virtual host of fluidd.";
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.nginx = {
-      enable = true;
-      upstreams.fluidd-apiserver.servers."${moonraker.address}:${toString moonraker.port}" = { };
-      virtualHosts."${cfg.hostName}" = mkMerge [
-        cfg.nginx
-        {
-          root = mkForce "${cfg.package}/share/fluidd/htdocs";
-          locations = {
-            "/" = {
-              index = "index.html";
-              tryFiles = "$uri $uri/ /index.html";
-            };
-            "/index.html".extraConfig = ''
-              add_header Cache-Control "no-store, no-cache, must-revalidate";
-            '';
-            "/websocket" = {
-              proxyWebsockets = true;
-              proxyPass = "http://fluidd-apiserver/websocket";
-            };
-            "~ ^/(printer|api|access|machine|server)/" = {
-              proxyWebsockets = true;
-              proxyPass = "http://fluidd-apiserver$request_uri";
-            };
-          };
-        }
-      ];
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/freshrss.nix b/nixos/modules/services/web-apps/freshrss.nix
deleted file mode 100644
index 021101fecaa4..000000000000
--- a/nixos/modules/services/web-apps/freshrss.nix
+++ /dev/null
@@ -1,304 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.freshrss;
-
-  poolName = "freshrss";
-in
-{
-  meta.maintainers = with maintainers; [ etu stunkymonkey mattchrist ];
-
-  options.services.freshrss = {
-    enable = mkEnableOption "FreshRSS RSS aggregator and reader with php-fpm backend.";
-
-    package = mkPackageOption pkgs "freshrss" { };
-
-    defaultUser = mkOption {
-      type = types.str;
-      default = "admin";
-      description = "Default username for FreshRSS.";
-      example = "eva";
-    };
-
-    passwordFile = mkOption {
-      type = types.nullOr types.path;
-      default = null;
-      description = "Password for the defaultUser for FreshRSS.";
-      example = "/run/secrets/freshrss";
-    };
-
-    baseUrl = mkOption {
-      type = types.str;
-      description = "Default URL for FreshRSS.";
-      example = "https://freshrss.example.com";
-    };
-
-    language = mkOption {
-      type = types.str;
-      default = "en";
-      description = "Default language for FreshRSS.";
-      example = "de";
-    };
-
-    database = {
-      type = mkOption {
-        type = types.enum [ "sqlite" "pgsql" "mysql" ];
-        default = "sqlite";
-        description = "Database type.";
-        example = "pgsql";
-      };
-
-      host = mkOption {
-        type = types.nullOr types.str;
-        default = "localhost";
-        description = "Database host for FreshRSS.";
-      };
-
-      port = mkOption {
-        type = types.nullOr types.port;
-        default = null;
-        description = "Database port for FreshRSS.";
-        example = 3306;
-      };
-
-      user = mkOption {
-        type = types.nullOr types.str;
-        default = "freshrss";
-        description = "Database user for FreshRSS.";
-      };
-
-      passFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = "Database password file for FreshRSS.";
-        example = "/run/secrets/freshrss";
-      };
-
-      name = mkOption {
-        type = types.nullOr types.str;
-        default = "freshrss";
-        description = "Database name for FreshRSS.";
-      };
-
-      tableprefix = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = "Database table prefix for FreshRSS.";
-        example = "freshrss";
-      };
-    };
-
-    dataDir = mkOption {
-      type = types.str;
-      default = "/var/lib/freshrss";
-      description = "Default data folder for FreshRSS.";
-      example = "/mnt/freshrss";
-    };
-
-    virtualHost = mkOption {
-      type = types.nullOr types.str;
-      default = "freshrss";
-      description = ''
-        Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
-      '';
-    };
-
-    pool = mkOption {
-      type = types.str;
-      default = poolName;
-      description = ''
-        Name of the php-fpm pool to use and setup. If not specified, a pool will be created
-        with default values.
-      '';
-    };
-
-    user = mkOption {
-      type = types.str;
-      default = "freshrss";
-      description = "User under which FreshRSS runs.";
-    };
-
-    authType = mkOption {
-      type = types.enum [ "form" "http_auth" "none" ];
-      default = "form";
-      description = "Authentication type for FreshRSS.";
-    };
-  };
-
-  config =
-    let
-      defaultServiceConfig = {
-        ReadWritePaths = "${cfg.dataDir}";
-        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
-        DeviceAllow = "";
-        LockPersonality = true;
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        ProcSubset = "pid";
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        ProtectSystem = "strict";
-        RemoveIPC = true;
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
-        UMask = "0007";
-        Type = "oneshot";
-        User = cfg.user;
-        Group = config.users.users.${cfg.user}.group;
-        StateDirectory = "freshrss";
-        WorkingDirectory = cfg.package;
-      };
-    in
-    mkIf cfg.enable {
-      assertions = mkIf (cfg.authType == "form") [
-        {
-          assertion = cfg.passwordFile != null;
-          message = ''
-            `passwordFile` must be supplied when using "form" authentication!
-          '';
-        }
-      ];
-      # Set up a Nginx virtual host.
-      services.nginx = mkIf (cfg.virtualHost != null) {
-        enable = true;
-        virtualHosts.${cfg.virtualHost} = {
-          root = "${cfg.package}/p";
-
-          # php files handling
-          # this regex is mandatory because of the API
-          locations."~ ^.+?\.php(/.*)?$".extraConfig = ''
-            fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket};
-            fastcgi_split_path_info ^(.+\.php)(/.*)$;
-            # By default, the variable PATH_INFO is not set under PHP-FPM
-            # But FreshRSS API greader.php need it. If you have a “Bad Request” error, double check this var!
-            # NOTE: the separate $path_info variable is required. For more details, see:
-            # https://trac.nginx.org/nginx/ticket/321
-            set $path_info $fastcgi_path_info;
-            fastcgi_param PATH_INFO $path_info;
-            include ${pkgs.nginx}/conf/fastcgi_params;
-            include ${pkgs.nginx}/conf/fastcgi.conf;
-          '';
-
-          locations."/" = {
-            tryFiles = "$uri $uri/ index.php";
-            index = "index.php index.html index.htm";
-          };
-        };
-      };
-
-      # Set up phpfpm pool
-      services.phpfpm.pools = mkIf (cfg.pool == poolName) {
-        ${poolName} = {
-          user = "freshrss";
-          settings = {
-            "listen.owner" = "nginx";
-            "listen.group" = "nginx";
-            "listen.mode" = "0600";
-            "pm" = "dynamic";
-            "pm.max_children" = 32;
-            "pm.max_requests" = 500;
-            "pm.start_servers" = 2;
-            "pm.min_spare_servers" = 2;
-            "pm.max_spare_servers" = 5;
-            "catch_workers_output" = true;
-          };
-          phpEnv = {
-            DATA_PATH = "${cfg.dataDir}";
-          };
-        };
-      };
-
-      users.users."${cfg.user}" = {
-        description = "FreshRSS service user";
-        isSystemUser = true;
-        group = "${cfg.user}";
-        home = cfg.dataDir;
-      };
-      users.groups."${cfg.user}" = { };
-
-      systemd.tmpfiles.settings."10-freshrss".${cfg.dataDir}.d = {
-        inherit (cfg) user;
-        group = config.users.users.${cfg.user}.group;
-      };
-
-      systemd.services.freshrss-config =
-        let
-          settingsFlags = concatStringsSep " \\\n    "
-            (mapAttrsToList (k: v: "${k} ${toString v}") {
-              "--default_user" = ''"${cfg.defaultUser}"'';
-              "--auth_type" = ''"${cfg.authType}"'';
-              "--base_url" = ''"${cfg.baseUrl}"'';
-              "--language" = ''"${cfg.language}"'';
-              "--db-type" = ''"${cfg.database.type}"'';
-              # The following attributes are optional depending on the type of
-              # database.  Those that evaluate to null on the left hand side
-              # will be omitted.
-              ${if cfg.database.name != null then "--db-base" else null} = ''"${cfg.database.name}"'';
-              ${if cfg.database.passFile != null then "--db-password" else null} = ''"$(cat ${cfg.database.passFile})"'';
-              ${if cfg.database.user != null then "--db-user" else null} = ''"${cfg.database.user}"'';
-              ${if cfg.database.tableprefix != null then "--db-prefix" else null} = ''"${cfg.database.tableprefix}"'';
-              ${if cfg.database.host != null && cfg.database.port != null then "--db-host" else null} = ''"${cfg.database.host}:${toString cfg.database.port}"'';
-            });
-        in
-        {
-          description = "Set up the state directory for FreshRSS before use";
-          wantedBy = [ "multi-user.target" ];
-          serviceConfig = defaultServiceConfig // {
-            RemainAfterExit = true;
-          };
-          restartIfChanged = true;
-          environment = {
-            DATA_PATH = cfg.dataDir;
-          };
-
-          script =
-            let
-              userScriptArgs = ''--user ${cfg.defaultUser} ${optionalString (cfg.authType == "form") ''--password "$(cat ${cfg.passwordFile})"''}'';
-              updateUserScript = optionalString (cfg.authType == "form" || cfg.authType == "none") ''
-                ./cli/update-user.php ${userScriptArgs}
-              '';
-              createUserScript = optionalString (cfg.authType == "form" || cfg.authType == "none") ''
-                ./cli/create-user.php ${userScriptArgs}
-              '';
-            in
-            ''
-              # do installation or reconfigure
-              if test -f ${cfg.dataDir}/config.php; then
-                # reconfigure with settings
-                ./cli/reconfigure.php ${settingsFlags}
-                ${updateUserScript}
-              else
-                # check correct folders in data folder
-                ./cli/prepare.php
-                # install with settings
-                ./cli/do-install.php ${settingsFlags}
-                ${createUserScript}
-              fi
-            '';
-        };
-
-      systemd.services.freshrss-updater = {
-        description = "FreshRSS feed updater";
-        after = [ "freshrss-config.service" ];
-        startAt = "*:0/5";
-        environment = {
-          DATA_PATH = cfg.dataDir;
-        };
-        serviceConfig = defaultServiceConfig // {
-          ExecStart = "${cfg.package}/app/actualize_script.php";
-        };
-      };
-    };
-}
diff --git a/nixos/modules/services/web-apps/galene.nix b/nixos/modules/services/web-apps/galene.nix
deleted file mode 100644
index 32854e757ac3..000000000000
--- a/nixos/modules/services/web-apps/galene.nix
+++ /dev/null
@@ -1,207 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.galene;
-  opt = options.services.galene;
-  defaultstateDir = "/var/lib/galene";
-  defaultrecordingsDir = "${cfg.stateDir}/recordings";
-  defaultgroupsDir = "${cfg.stateDir}/groups";
-  defaultdataDir = "${cfg.stateDir}/data";
-in
-{
-  options = {
-    services.galene = {
-      enable = mkEnableOption "Galene Service";
-
-      stateDir = mkOption {
-        default = defaultstateDir;
-        type = types.str;
-        description = ''
-          The directory where Galene stores its internal state. If left as the default
-          value this directory will automatically be created before the Galene server
-          starts, otherwise the sysadmin is responsible for ensuring the directory
-          exists with appropriate ownership and permissions.
-        '';
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "galene";
-        description = "User account under which galene runs.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "galene";
-        description = "Group under which galene runs.";
-      };
-
-      insecure = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether Galene should listen in http or in https. If left as the default
-          value (false), Galene needs to be fed a private key and a certificate.
-        '';
-      };
-
-      certFile = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/path/to/your/cert.pem";
-        description = ''
-          Path to the server's certificate. The file is copied at runtime to
-          Galene's data directory where it needs to reside.
-        '';
-      };
-
-      keyFile = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/path/to/your/key.pem";
-        description = ''
-          Path to the server's private key. The file is copied at runtime to
-          Galene's data directory where it needs to reside.
-        '';
-      };
-
-      httpAddress = mkOption {
-        type = types.str;
-        default = "";
-        description = "HTTP listen address for galene.";
-      };
-
-      httpPort = mkOption {
-        type = types.port;
-        default = 8443;
-        description = "HTTP listen port.";
-      };
-
-      staticDir = mkOption {
-        type = types.str;
-        default = "${cfg.package.static}/static";
-        defaultText = literalExpression ''"''${package.static}/static"'';
-        example = "/var/lib/galene/static";
-        description = "Web server directory.";
-      };
-
-      recordingsDir = mkOption {
-        type = types.str;
-        default = defaultrecordingsDir;
-        defaultText = literalExpression ''"''${config.${opt.stateDir}}/recordings"'';
-        example = "/var/lib/galene/recordings";
-        description = "Recordings directory.";
-      };
-
-      dataDir = mkOption {
-        type = types.str;
-        default = defaultdataDir;
-        defaultText = literalExpression ''"''${config.${opt.stateDir}}/data"'';
-        example = "/var/lib/galene/data";
-        description = "Data directory.";
-      };
-
-      groupsDir = mkOption {
-        type = types.str;
-        default = defaultgroupsDir;
-        defaultText = literalExpression ''"''${config.${opt.stateDir}}/groups"'';
-        example = "/var/lib/galene/groups";
-        description = "Web server directory.";
-      };
-
-      package = mkPackageOption pkgs "galene" { };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = cfg.insecure || (cfg.certFile != null && cfg.keyFile != null);
-        message = ''
-          Galene needs both certFile and keyFile defined for encryption, or
-          the insecure flag.
-        '';
-      }
-    ];
-
-    systemd.services.galene = {
-      description = "galene";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      preStart = ''
-        ${optionalString (cfg.insecure != true) ''
-           install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.certFile} ${cfg.dataDir}/cert.pem
-           install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.keyFile} ${cfg.dataDir}/key.pem
-        ''}
-      '';
-
-      serviceConfig = mkMerge [
-        {
-          Type = "simple";
-          User = cfg.user;
-          Group = cfg.group;
-          WorkingDirectory = cfg.stateDir;
-          ExecStart = ''${cfg.package}/bin/galene \
-          ${optionalString (cfg.insecure) "-insecure"} \
-          -data ${cfg.dataDir} \
-          -groups ${cfg.groupsDir} \
-          -recordings ${cfg.recordingsDir} \
-          -static ${cfg.staticDir}'';
-          Restart = "always";
-          # Upstream Requirements
-          LimitNOFILE = 65536;
-          StateDirectory = [ ] ++
-            optional (cfg.stateDir == defaultstateDir) "galene" ++
-            optional (cfg.dataDir == defaultdataDir) "galene/data" ++
-            optional (cfg.groupsDir == defaultgroupsDir) "galene/groups" ++
-            optional (cfg.recordingsDir == defaultrecordingsDir) "galene/recordings";
-
-          # Hardening
-          CapabilityBoundingSet = [ "" ];
-          DeviceAllow = [ "" ];
-          LockPersonality = true;
-          MemoryDenyWriteExecute = true;
-          NoNewPrivileges = true;
-          PrivateDevices = true;
-          PrivateTmp = true;
-          PrivateUsers = true;
-          ProcSubset = "pid";
-          ProtectClock = true;
-          ProtectControlGroups = true;
-          ProtectHome = true;
-          ProtectHostname = true;
-          ProtectKernelLogs = true;
-          ProtectKernelModules = true;
-          ProtectKernelTunables = true;
-          ProtectProc = "invisible";
-          ProtectSystem = "strict";
-          ReadWritePaths = cfg.recordingsDir;
-          RemoveIPC = true;
-          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" ];
-          RestrictNamespaces = true;
-          RestrictRealtime = true;
-          RestrictSUIDSGID = true;
-          SystemCallArchitectures = "native";
-          SystemCallFilter = [ "@system-service" "~@privileged" ];
-          UMask = "0077";
-        }
-      ];
-    };
-
-    users.users = mkIf (cfg.user == "galene")
-      {
-        galene = {
-          description = "galene Service";
-          group = cfg.group;
-          isSystemUser = true;
-        };
-      };
-
-    users.groups = mkIf (cfg.group == "galene") {
-      galene = { };
-    };
-  };
-  meta.maintainers = with lib.maintainers; [ rgrunbla ];
-}
diff --git a/nixos/modules/services/web-apps/gerrit.nix b/nixos/modules/services/web-apps/gerrit.nix
deleted file mode 100644
index 573c9d0d7dbb..000000000000
--- a/nixos/modules/services/web-apps/gerrit.nix
+++ /dev/null
@@ -1,232 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.gerrit;
-
-  # NixOS option type for git-like configs
-  gitIniType = with types;
-    let
-      primitiveType = either str (either bool int);
-      multipleType = either primitiveType (listOf primitiveType);
-      sectionType = lazyAttrsOf multipleType;
-      supersectionType = lazyAttrsOf (either multipleType sectionType);
-    in lazyAttrsOf supersectionType;
-
-  gerritConfig = pkgs.writeText "gerrit.conf" (
-    lib.generators.toGitINI cfg.settings
-  );
-
-  replicationConfig = pkgs.writeText "replication.conf" (
-    lib.generators.toGitINI cfg.replicationSettings
-  );
-
-  # Wrap the gerrit java with all the java options so it can be called
-  # like a normal CLI app
-  gerrit-cli = pkgs.writeShellScriptBin "gerrit" ''
-    set -euo pipefail
-    jvmOpts=(
-      ${lib.escapeShellArgs cfg.jvmOpts}
-      -Xmx${cfg.jvmHeapLimit}
-    )
-    exec ${cfg.jvmPackage}/bin/java \
-      "''${jvmOpts[@]}" \
-      -jar ${cfg.package}/webapps/${cfg.package.name}.war \
-      "$@"
-  '';
-
-  gerrit-plugins = pkgs.runCommand
-    "gerrit-plugins"
-    {
-      buildInputs = [ gerrit-cli ];
-    }
-    ''
-      shopt -s nullglob
-      mkdir $out
-
-      for name in ${toString cfg.builtinPlugins}; do
-        echo "Installing builtin plugin $name.jar"
-        gerrit cat plugins/$name.jar > $out/$name.jar
-      done
-
-      for file in ${toString cfg.plugins}; do
-        name=$(echo "$file" | cut -d - -f 2-)
-        echo "Installing plugin $name"
-        ln -sf "$file" $out/$name
-      done
-    '';
-in
-{
-  options = {
-    services.gerrit = {
-      enable = mkEnableOption "Gerrit service";
-
-      package = mkPackageOption pkgs "gerrit" { };
-
-      jvmPackage = mkPackageOption pkgs "jre_headless" { };
-
-      jvmOpts = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"
-          "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance"
-        ];
-        description = "A list of JVM options to start gerrit with.";
-      };
-
-      jvmHeapLimit = mkOption {
-        type = types.str;
-        default = "1024m";
-        description = ''
-          How much memory to allocate to the JVM heap
-        '';
-      };
-
-      listenAddress = mkOption {
-        type = types.str;
-        default = "[::]:8080";
-        description = ''
-          `hostname:port` to listen for HTTP traffic.
-
-          This is bound using the systemd socket activation.
-        '';
-      };
-
-      settings = mkOption {
-        type = gitIniType;
-        default = {};
-        description = ''
-          Gerrit configuration. This will be generated to the
-          `etc/gerrit.config` file.
-        '';
-      };
-
-      replicationSettings = mkOption {
-        type = gitIniType;
-        default = {};
-        description = ''
-          Replication configuration. This will be generated to the
-          `etc/replication.config` file.
-        '';
-      };
-
-      plugins = mkOption {
-        type = types.listOf types.package;
-        default = [];
-        description = ''
-          List of plugins to add to Gerrit. Each derivation is a jar file
-          itself where the name of the derivation is the name of plugin.
-        '';
-      };
-
-      builtinPlugins = mkOption {
-        type = types.listOf (types.enum cfg.package.passthru.plugins);
-        default = [];
-        description = ''
-          List of builtins plugins to install. Those are shipped in the
-          `gerrit.war` file.
-        '';
-      };
-
-      serverId = mkOption {
-        type = types.str;
-        description = ''
-          Set a UUID that uniquely identifies the server.
-
-          This can be generated with
-          `nix-shell -p util-linux --run uuidgen`.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    assertions = [
-      {
-        assertion = cfg.replicationSettings != {} -> elem "replication" cfg.builtinPlugins;
-        message = "Gerrit replicationSettings require enabling the replication plugin";
-      }
-    ];
-
-    services.gerrit.settings = {
-      cache.directory = "/var/cache/gerrit";
-      container.heapLimit = cfg.jvmHeapLimit;
-      gerrit.basePath = lib.mkDefault "git";
-      gerrit.serverId = cfg.serverId;
-      httpd.inheritChannel = "true";
-      httpd.listenUrl = lib.mkDefault "http://${cfg.listenAddress}";
-      index.type = lib.mkDefault "lucene";
-    };
-
-    # Add the gerrit CLI to the system to run `gerrit init` and friends.
-    environment.systemPackages = [ gerrit-cli ];
-
-    systemd.sockets.gerrit = {
-      unitConfig.Description = "Gerrit HTTP socket";
-      wantedBy = [ "sockets.target" ];
-      listenStreams = [ cfg.listenAddress ];
-    };
-
-    systemd.services.gerrit = {
-      description = "Gerrit";
-
-      wantedBy = [ "multi-user.target" ];
-      requires = [ "gerrit.socket" ];
-      after = [ "gerrit.socket" "network.target" ];
-
-      path = [
-        gerrit-cli
-        pkgs.bash
-        pkgs.coreutils
-        pkgs.git
-        pkgs.openssh
-      ];
-
-      environment = {
-        GERRIT_HOME = "%S/gerrit";
-        GERRIT_TMP = "%T";
-        HOME = "%S/gerrit";
-        XDG_CONFIG_HOME = "%S/gerrit/.config";
-      };
-
-      preStart = ''
-        set -euo pipefail
-
-        # bootstrap if nothing exists
-        if [[ ! -d git ]]; then
-          gerrit init --batch --no-auto-start
-        fi
-
-        # install gerrit.war for the plugin manager
-        rm -rf bin
-        mkdir bin
-        ln -sfv ${cfg.package}/webapps/${cfg.package.name}.war bin/gerrit.war
-
-        # copy the config, keep it mutable because Gerrit
-        ln -sfv ${gerritConfig} etc/gerrit.config
-        ln -sfv ${replicationConfig} etc/replication.config
-
-        # install the plugins
-        rm -rf plugins
-        ln -sv ${gerrit-plugins} plugins
-      ''
-      ;
-
-      serviceConfig = {
-        CacheDirectory = "gerrit";
-        DynamicUser = true;
-        ExecStart = "${gerrit-cli}/bin/gerrit daemon --console-log";
-        LimitNOFILE = 4096;
-        StandardInput = "socket";
-        StandardOutput = "journal";
-        StateDirectory = "gerrit";
-        WorkingDirectory = "%S/gerrit";
-      };
-    };
-  };
-
-  meta.maintainers = with lib.maintainers; [ edef zimbatm ];
-  # uses attributes of the linked package
-  meta.buildDocsInSandbox = false;
-}
diff --git a/nixos/modules/services/web-apps/gotify-server.nix b/nixos/modules/services/web-apps/gotify-server.nix
deleted file mode 100644
index b700fd14ee52..000000000000
--- a/nixos/modules/services/web-apps/gotify-server.nix
+++ /dev/null
@@ -1,49 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-with lib;
-
-let
-  cfg = config.services.gotify;
-in {
-  options = {
-    services.gotify = {
-      enable = mkEnableOption "Gotify webserver";
-
-      port = mkOption {
-        type = types.port;
-        description = ''
-          Port the server listens to.
-        '';
-      };
-
-      stateDirectoryName = mkOption {
-        type = types.str;
-        default = "gotify-server";
-        description = ''
-          The name of the directory below {file}`/var/lib` where
-          gotify stores its runtime data.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.gotify-server = {
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-      description = "Simple server for sending and receiving messages";
-
-      environment = {
-        GOTIFY_SERVER_PORT = toString cfg.port;
-      };
-
-      serviceConfig = {
-        WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
-        StateDirectory = cfg.stateDirectoryName;
-        Restart = "always";
-        DynamicUser = "yes";
-        ExecStart = "${pkgs.gotify-server}/bin/server";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/gotosocial.md b/nixos/modules/services/web-apps/gotosocial.md
deleted file mode 100644
index b3540f0d5811..000000000000
--- a/nixos/modules/services/web-apps/gotosocial.md
+++ /dev/null
@@ -1,68 +0,0 @@
-# GoToSocial {#module-services-gotosocial}
-
-[GoToSocial](https://gotosocial.org/) is an ActivityPub social network server, written in Golang.
-
-## Service configuration {#modules-services-gotosocial-service-configuration}
-
-The following configuration sets up the PostgreSQL as database backend and binds
-GoToSocial to `127.0.0.1:8080`, expecting to be run behind a HTTP proxy on `gotosocial.example.com`.
-
-```nix
-{
-  services.gotosocial = {
-    enable = true;
-    setupPostgresqlDB = true;
-    settings = {
-      application-name = "My GoToSocial";
-      host = "gotosocial.example.com";
-      protocol = "https";
-      bind-address = "127.0.0.1";
-      port = 8080;
-    };
-  };
-}
-```
-
-Please refer to the [GoToSocial Documentation](https://docs.gotosocial.org/en/latest/configuration/general/)
-for additional configuration options.
-
-## Proxy configuration {#modules-services-gotosocial-proxy-configuration}
-
-Although it is possible to expose GoToSocial directly, it is common practice to operate it behind an
-HTTP reverse proxy such as nginx.
-
-```nix
-{
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-  services.nginx = {
-    enable = true;
-    clientMaxBodySize = "40M";
-    virtualHosts = with config.services.gotosocial.settings; {
-      "${host}" = {
-        enableACME = true;
-        forceSSL = true;
-        locations = {
-          "/" = {
-            recommendedProxySettings = true;
-            proxyWebsockets = true;
-            proxyPass = "http://${bind-address}:${toString port}";
-          };
-        };
-      };
-    };
-  };
-}
-```
-
-Please refer to [](#module-security-acme) for details on how to provision an SSL/TLS certificate.
-
-## User management {#modules-services-gotosocial-user-management}
-
-After the GoToSocial service is running, the `gotosocial-admin` utility can be used to manage users. In particular an
-administrative user can be created with
-
-```ShellSession
-$ sudo gotosocial-admin account create --username <nickname> --email <email> --password <password>
-$ sudo gotosocial-admin account confirm --username <nickname>
-$ sudo gotosocial-admin account promote --username <nickname>
-```
diff --git a/nixos/modules/services/web-apps/gotosocial.nix b/nixos/modules/services/web-apps/gotosocial.nix
deleted file mode 100644
index aee1edf66a6a..000000000000
--- a/nixos/modules/services/web-apps/gotosocial.nix
+++ /dev/null
@@ -1,171 +0,0 @@
-{ config, lib, pkgs, ... }:
-let
-  cfg = config.services.gotosocial;
-  settingsFormat = pkgs.formats.yaml { };
-  configFile = settingsFormat.generate "config.yml" cfg.settings;
-  defaultSettings = {
-    application-name = "gotosocial";
-
-    protocol = "https";
-
-    bind-address = "127.0.0.1";
-    port = 8080;
-
-    storage-local-base-path = "/var/lib/gotosocial/storage";
-
-    db-type = "sqlite";
-    db-address = "/var/lib/gotosocial/database.sqlite";
-  };
-  gotosocial-admin = pkgs.writeShellScriptBin "gotosocial-admin" ''
-    exec systemd-run \
-      -u gotosocial-admin.service \
-      -p Group=gotosocial \
-      -p User=gotosocial \
-      -q -t -G --wait --service-type=exec \
-      ${cfg.package}/bin/gotosocial --config-path ${configFile} admin "$@"
-  '';
-in
-{
-  meta.doc = ./gotosocial.md;
-  meta.maintainers = with lib.maintainers; [ blakesmith ];
-
-  options.services.gotosocial = {
-    enable = lib.mkEnableOption "ActivityPub social network server";
-
-    package = lib.mkPackageOption pkgs "gotosocial" { };
-
-    openFirewall = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = ''
-        Open the configured port in the firewall.
-        Using a reverse proxy instead is highly recommended.
-      '';
-    };
-
-    setupPostgresqlDB = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = ''
-        Whether to setup a local postgres database and populate the
-        `db-type` fields in `services.gotosocial.settings`.
-      '';
-    };
-
-    settings = lib.mkOption {
-      type = settingsFormat.type;
-      default = defaultSettings;
-      example = {
-        application-name = "My GoToSocial";
-        host = "gotosocial.example.com";
-      };
-      description = ''
-        Contents of the GoToSocial YAML config.
-
-        Please refer to the
-        [documentation](https://docs.gotosocial.org/en/latest/configuration/)
-        and
-        [example config](https://github.com/superseriousbusiness/gotosocial/blob/main/example/config.yaml).
-
-        Please note that the `host` option cannot be changed later so it is important to configure this correctly before you start GoToSocial.
-      '';
-    };
-
-    environmentFile = lib.mkOption {
-      type = lib.types.nullOr lib.types.path;
-      description = ''
-        File path containing environment variables for configuring the GoToSocial service
-        in the format of an EnvironmentFile as described by systemd.exec(5).
-
-        This option could be used to pass sensitive configuration to the GoToSocial daemon.
-
-        Please refer to the Environment Variables section in the
-        [documentation](https://docs.gotosocial.org/en/latest/configuration/).
-      '';
-      default = null;
-      example = "/root/nixos/secrets/gotosocial.env";
-    };
-
-  };
-
-  config = lib.mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = cfg.settings.host or null != null;
-        message = ''
-          You have to define a hostname for GoToSocial (`services.gotosocial.settings.host`), it cannot be changed later without starting over!
-        '';
-      }
-    ];
-
-    services.gotosocial.settings = (lib.mapAttrs (name: lib.mkDefault) (
-      defaultSettings // {
-        web-asset-base-dir = "${cfg.package}/share/gotosocial/web/assets/";
-        web-template-base-dir = "${cfg.package}/share/gotosocial/web/template/";
-      }
-    )) // (lib.optionalAttrs cfg.setupPostgresqlDB {
-      db-type = "postgres";
-      db-address = "/run/postgresql";
-      db-database = "gotosocial";
-      db-user = "gotosocial";
-    });
-
-    environment.systemPackages = [ gotosocial-admin ];
-
-    users.groups.gotosocial = { };
-    users.users.gotosocial = {
-      group = "gotosocial";
-      isSystemUser = true;
-    };
-
-    networking.firewall = lib.mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.settings.port ];
-    };
-
-    services.postgresql = lib.mkIf cfg.setupPostgresqlDB {
-      enable = true;
-      ensureDatabases = [ "gotosocial" ];
-      ensureUsers = [
-        {
-          name = "gotosocial";
-          ensureDBOwnership = true;
-        }
-      ];
-    };
-
-    systemd.services.gotosocial = {
-      description = "ActivityPub social network server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ]
-        ++ lib.optional cfg.setupPostgresqlDB "postgresql.service";
-      requires = lib.optional cfg.setupPostgresqlDB "postgresql.service";
-      restartTriggers = [ configFile ];
-
-      serviceConfig = {
-        EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
-        ExecStart = "${cfg.package}/bin/gotosocial --config-path ${configFile} server start";
-        Restart = "on-failure";
-        Group = "gotosocial";
-        User = "gotosocial";
-        StateDirectory = "gotosocial";
-        WorkingDirectory = "/var/lib/gotosocial";
-
-        # Security options:
-        # Based on https://github.com/superseriousbusiness/gotosocial/blob/v0.8.1/example/gotosocial.service
-        AmbientCapabilities = lib.optional (cfg.settings.port < 1024) "CAP_NET_BIND_SERVICE";
-        NoNewPrivileges = true;
-        PrivateTmp = true;
-        PrivateDevices = true;
-        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        DevicePolicy = "closed";
-        ProtectSystem = "full";
-        ProtectControlGroups = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        LockPersonality = true;
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/grocy.md b/nixos/modules/services/web-apps/grocy.md
deleted file mode 100644
index f4b5769c2479..000000000000
--- a/nixos/modules/services/web-apps/grocy.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Grocy {#module-services-grocy}
-
-[Grocy](https://grocy.info/) is a web-based self-hosted groceries
-& household management solution for your home.
-
-## Basic usage {#module-services-grocy-basic-usage}
-
-A very basic configuration may look like this:
-```nix
-{ pkgs, ... }:
-{
-  services.grocy = {
-    enable = true;
-    hostName = "grocy.tld";
-  };
-}
-```
-This configures a simple vhost using [nginx](#opt-services.nginx.enable)
-which listens to `grocy.tld` with fully configured ACME/LE (this can be
-disabled by setting [services.grocy.nginx.enableSSL](#opt-services.grocy.nginx.enableSSL)
-to `false`). After the initial setup the credentials `admin:admin`
-can be used to login.
-
-The application's state is persisted at `/var/lib/grocy/grocy.db` in a
-`sqlite3` database. The migration is applied when requesting the `/`-route
-of the application.
-
-## Settings {#module-services-grocy-settings}
-
-The configuration for `grocy` is located at `/etc/grocy/config.php`.
-By default, the following settings can be defined in the NixOS-configuration:
-```nix
-{ pkgs, ... }:
-{
-  services.grocy.settings = {
-    # The default currency in the system for invoices etc.
-    # Please note that exchange rates aren't taken into account, this
-    # is just the setting for what's shown in the frontend.
-    currency = "EUR";
-
-    # The display language (and locale configuration) for grocy.
-    culture = "de";
-
-    calendar = {
-      # Whether or not to show the week-numbers
-      # in the calendar.
-      showWeekNumber = true;
-
-      # Index of the first day to be shown in the calendar (0=Sunday, 1=Monday,
-      # 2=Tuesday and so on).
-      firstDayOfWeek = 2;
-    };
-  };
-}
-```
-
-If you want to alter the configuration file on your own, you can do this manually with
-an expression like this:
-```nix
-{ lib, ... }:
-{
-  environment.etc."grocy/config.php".text = lib.mkAfter ''
-    // Arbitrary PHP code in grocy's configuration file
-  '';
-}
-```
diff --git a/nixos/modules/services/web-apps/grocy.nix b/nixos/modules/services/web-apps/grocy.nix
deleted file mode 100644
index eb4feb191aa5..000000000000
--- a/nixos/modules/services/web-apps/grocy.nix
+++ /dev/null
@@ -1,184 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.grocy;
-in {
-  options.services.grocy = {
-    enable = mkEnableOption "grocy";
-
-    package = mkPackageOption pkgs "grocy" { };
-
-    hostName = mkOption {
-      type = types.str;
-      description = ''
-        FQDN for the grocy instance.
-      '';
-    };
-
-    nginx.enableSSL = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Whether or not to enable SSL (with ACME and let's encrypt)
-        for the grocy vhost.
-      '';
-    };
-
-    phpfpm.settings = mkOption {
-      type = with types; attrsOf (oneOf [ int str bool ]);
-      default = {
-        "pm" = "dynamic";
-        "php_admin_value[error_log]" = "stderr";
-        "php_admin_flag[log_errors]" = true;
-        "listen.owner" = "nginx";
-        "catch_workers_output" = true;
-        "pm.max_children" = "32";
-        "pm.start_servers" = "2";
-        "pm.min_spare_servers" = "2";
-        "pm.max_spare_servers" = "4";
-        "pm.max_requests" = "500";
-      };
-
-      description = ''
-        Options for grocy's PHPFPM pool.
-      '';
-    };
-
-    dataDir = mkOption {
-      type = types.str;
-      default = "/var/lib/grocy";
-      description = ''
-        Home directory of the `grocy` user which contains
-        the application's state.
-      '';
-    };
-
-    settings = {
-      currency = mkOption {
-        type = types.str;
-        default = "USD";
-        example = "EUR";
-        description = ''
-          ISO 4217 code for the currency to display.
-        '';
-      };
-
-      culture = mkOption {
-        type = types.enum [ "de" "en" "da" "en_GB" "es" "fr" "hu" "it" "nl" "no" "pl" "pt_BR" "ru" "sk_SK" "sv_SE" "tr" ];
-        default = "en";
-        description = ''
-          Display language of the frontend.
-        '';
-      };
-
-      calendar = {
-        showWeekNumber = mkOption {
-          default = true;
-          type = types.bool;
-          description = ''
-            Show the number of the weeks in the calendar views.
-          '';
-        };
-        firstDayOfWeek = mkOption {
-          default = null;
-          type = types.nullOr (types.enum (range 0 6));
-          description = ''
-            Which day of the week (0=Sunday, 1=Monday etc.) should be the
-            first day.
-          '';
-        };
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment.etc."grocy/config.php".text = ''
-      <?php
-      Setting('CULTURE', '${cfg.settings.culture}');
-      Setting('CURRENCY', '${cfg.settings.currency}');
-      Setting('CALENDAR_FIRST_DAY_OF_WEEK', '${toString cfg.settings.calendar.firstDayOfWeek}');
-      Setting('CALENDAR_SHOW_WEEK_OF_YEAR', ${boolToString cfg.settings.calendar.showWeekNumber});
-    '';
-
-    users.users.grocy = {
-      isSystemUser = true;
-      createHome = true;
-      home = cfg.dataDir;
-      group = "nginx";
-    };
-
-    systemd.tmpfiles.rules = map (
-      dirName: "d '${cfg.dataDir}/${dirName}' - grocy nginx - -"
-    ) [ "viewcache" "plugins" "settingoverrides" "storage" ];
-
-    services.phpfpm.pools.grocy = {
-      user = "grocy";
-      group = "nginx";
-
-      # PHP 8.1 and 8.2 are the only version which are supported/tested by upstream:
-      # https://github.com/grocy/grocy/blob/v4.0.2/README.md#platform-support
-      phpPackage = pkgs.php82;
-
-      inherit (cfg.phpfpm) settings;
-
-      phpEnv = {
-        GROCY_CONFIG_FILE = "/etc/grocy/config.php";
-        GROCY_DB_FILE = "${cfg.dataDir}/grocy.db";
-        GROCY_STORAGE_DIR = "${cfg.dataDir}/storage";
-        GROCY_PLUGIN_DIR = "${cfg.dataDir}/plugins";
-        GROCY_CACHE_DIR = "${cfg.dataDir}/viewcache";
-      };
-    };
-
-    # After an update of grocy, the viewcache needs to be deleted. Otherwise grocy will not work
-    # https://github.com/grocy/grocy#how-to-update
-    systemd.services.grocy-setup = {
-      wantedBy = [ "multi-user.target" ];
-      before = [ "phpfpm-grocy.service" ];
-      script = ''
-        rm -rf ${cfg.dataDir}/viewcache/*
-      '';
-    };
-
-    services.nginx = {
-      enable = true;
-      virtualHosts."${cfg.hostName}" = mkMerge [
-        { root = "${cfg.package}/public";
-          locations."/".extraConfig = ''
-            rewrite ^ /index.php;
-          '';
-          locations."~ \\.php$".extraConfig = ''
-            fastcgi_split_path_info ^(.+\.php)(/.+)$;
-            fastcgi_pass unix:${config.services.phpfpm.pools.grocy.socket};
-            include ${config.services.nginx.package}/conf/fastcgi.conf;
-            include ${config.services.nginx.package}/conf/fastcgi_params;
-          '';
-          locations."~ \\.(js|css|ttf|woff2?|png|jpe?g|svg)$".extraConfig = ''
-            add_header Cache-Control "public, max-age=15778463";
-            add_header X-Content-Type-Options nosniff;
-            add_header X-XSS-Protection "1; mode=block";
-            add_header X-Robots-Tag none;
-            add_header X-Download-Options noopen;
-            add_header X-Permitted-Cross-Domain-Policies none;
-            add_header Referrer-Policy no-referrer;
-            access_log off;
-          '';
-          extraConfig = ''
-            try_files $uri /index.php;
-          '';
-        }
-        (mkIf cfg.nginx.enableSSL {
-          enableACME = true;
-          forceSSL = true;
-        })
-      ];
-    };
-  };
-
-  meta = {
-    maintainers = with maintainers; [ n0emis ];
-    doc = ./grocy.md;
-  };
-}
diff --git a/nixos/modules/services/web-apps/guacamole-client.nix b/nixos/modules/services/web-apps/guacamole-client.nix
deleted file mode 100644
index 98a6cac34f3d..000000000000
--- a/nixos/modules/services/web-apps/guacamole-client.nix
+++ /dev/null
@@ -1,60 +0,0 @@
-{ config
-, lib
-, pkgs
-, ...
-}:
-let
-  cfg = config.services.guacamole-client;
-  settingsFormat = pkgs.formats.javaProperties { };
-in
-{
-  options = {
-    services.guacamole-client = {
-      enable = lib.mkEnableOption "Apache Guacamole Client (Tomcat)";
-      package = lib.mkPackageOption pkgs "guacamole-client" { };
-
-      settings = lib.mkOption {
-        type = lib.types.submodule {
-          freeformType = settingsFormat.type;
-        };
-        default = {
-          guacd-hostname = "localhost";
-          guacd-port = 4822;
-        };
-        description = ''
-          Configuration written to `guacamole.properties`.
-
-          ::: {.note}
-          The Guacamole web application uses one main configuration file called
-          `guacamole.properties`. This file is the common location for all
-          configuration properties read by Guacamole or any extension of
-          Guacamole, including authentication providers.
-          :::
-        '';
-      };
-
-      enableWebserver = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = ''
-          Enable the Guacamole web application in a Tomcat webserver.
-        '';
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    environment.etc."guacamole/guacamole.properties" = lib.mkIf
-      (cfg.settings != {})
-      { source = (settingsFormat.generate "guacamole.properties" cfg.settings); };
-
-    services = lib.mkIf cfg.enableWebserver {
-      tomcat = {
-        enable = true;
-        webapps = [
-          cfg.package
-        ];
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/guacamole-server.nix b/nixos/modules/services/web-apps/guacamole-server.nix
deleted file mode 100644
index 6f6d12e9939f..000000000000
--- a/nixos/modules/services/web-apps/guacamole-server.nix
+++ /dev/null
@@ -1,83 +0,0 @@
-{ config
-, lib
-, pkgs
-, ...
-}:
-let
-  cfg = config.services.guacamole-server;
-in
-{
-  options = {
-    services.guacamole-server = {
-      enable = lib.mkEnableOption "Apache Guacamole Server (guacd)";
-      package = lib.mkPackageOption pkgs "guacamole-server" { };
-
-      extraEnvironment = lib.mkOption {
-        type = lib.types.attrsOf lib.types.str;
-        default = { };
-        example = lib.literalExpression ''
-          {
-            ENVIRONMENT = "production";
-          }
-        '';
-        description = "Environment variables to pass to guacd.";
-      };
-
-      host = lib.mkOption {
-        default = "127.0.0.1";
-        description = ''
-          The host name or IP address the server should listen to.
-        '';
-        type = lib.types.str;
-      };
-
-      port = lib.mkOption {
-        default = 4822;
-        description = ''
-          The port the guacd server should listen to.
-        '';
-        type = lib.types.port;
-      };
-
-      logbackXml = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/path/to/logback.xml";
-        description = ''
-          Configuration file that correspond to `logback.xml`.
-        '';
-      };
-
-      userMappingXml = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/path/to/user-mapping.xml";
-        description = ''
-          Configuration file that correspond to `user-mapping.xml`.
-        '';
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    # Setup configuration files.
-    environment.etc."guacamole/logback.xml" = lib.mkIf (cfg.logbackXml != null) { source = cfg.logbackXml; };
-    environment.etc."guacamole/user-mapping.xml" = lib.mkIf (cfg.userMappingXml != null) { source = cfg.userMappingXml; };
-
-    systemd.services.guacamole-server = {
-      description = "Apache Guacamole server (guacd)";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-      environment = {
-        HOME = "/run/guacamole-server";
-      } // cfg.extraEnvironment;
-      serviceConfig = {
-        ExecStart = "${lib.getExe cfg.package} -f -b ${cfg.host} -l ${toString cfg.port}";
-        RuntimeDirectory = "guacamole-server";
-        DynamicUser = true;
-        PrivateTmp = "yes";
-        Restart = "on-failure";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/healthchecks.nix b/nixos/modules/services/web-apps/healthchecks.nix
deleted file mode 100644
index 5562b37e502c..000000000000
--- a/nixos/modules/services/web-apps/healthchecks.nix
+++ /dev/null
@@ -1,271 +0,0 @@
-{ config, lib, options, pkgs, buildEnv, ... }:
-
-with lib;
-
-let
-  defaultUser = "healthchecks";
-  cfg = config.services.healthchecks;
-  opt = options.services.healthchecks;
-  pkg = cfg.package;
-  boolToPython = b: if b then "True" else "False";
-  environment = {
-    PYTHONPATH = pkg.pythonPath;
-    STATIC_ROOT = cfg.dataDir + "/static";
-  } // cfg.settings;
-
-  environmentFile = pkgs.writeText "healthchecks-environment" (lib.generators.toKeyValue { } environment);
-
-  healthchecksManageScript = pkgs.writeShellScriptBin "healthchecks-manage" ''
-    sudo=exec
-    if [[ "$USER" != "${cfg.user}" ]]; then
-      sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env --preserve-env=PYTHONPATH'
-    fi
-    export $(cat ${environmentFile} | xargs)
-    $sudo ${pkg}/opt/healthchecks/manage.py "$@"
-  '';
-in
-{
-  options.services.healthchecks = {
-    enable = mkEnableOption "healthchecks" // {
-      description = ''
-        Enable healthchecks.
-        It is expected to be run behind a HTTP reverse proxy.
-      '';
-    };
-
-    package = mkPackageOption pkgs "healthchecks" { };
-
-    user = mkOption {
-      default = defaultUser;
-      type = types.str;
-      description = ''
-        User account under which healthchecks runs.
-
-        ::: {.note}
-        If left as the default value this user will automatically be created
-        on system activation, otherwise you are responsible for
-        ensuring the user exists before the healthchecks service starts.
-        :::
-      '';
-    };
-
-    group = mkOption {
-      default = defaultUser;
-      type = types.str;
-      description = ''
-        Group account under which healthchecks runs.
-
-        ::: {.note}
-        If left as the default value this group will automatically be created
-        on system activation, otherwise you are responsible for
-        ensuring the group exists before the healthchecks service starts.
-        :::
-      '';
-    };
-
-    listenAddress = mkOption {
-      type = types.str;
-      default = "localhost";
-      description = "Address the server will listen on.";
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 8000;
-      description = "Port the server will listen on.";
-    };
-
-    dataDir = mkOption {
-      type = types.str;
-      default = "/var/lib/healthchecks";
-      description = ''
-        The directory used to store all data for healthchecks.
-
-        ::: {.note}
-        If left as the default value this directory will automatically be created before
-        the healthchecks server starts, otherwise you are responsible for ensuring the
-        directory exists with appropriate ownership and permissions.
-        :::
-      '';
-    };
-
-    settings = lib.mkOption {
-      description = ''
-        Environment variables which are read by healthchecks `(local)_settings.py`.
-
-        Settings which are explicitly covered in options below, are type-checked and/or transformed
-        before added to the environment, everything else is passed as a string.
-
-        See <https://healthchecks.io/docs/self_hosted_configuration/>
-        for a full documentation of settings.
-
-        We add additional variables to this list inside the packages `local_settings.py.`
-        - `STATIC_ROOT` to set a state directory for dynamically generated static files.
-        - `SECRET_KEY_FILE` to read `SECRET_KEY` from a file at runtime and keep it out of
-          /nix/store.
-        - `_FILE` variants for several values that hold sensitive information in
-          [Healthchecks configuration](https://healthchecks.io/docs/self_hosted_configuration/) so
-          that they also can be read from a file and kept out of /nix/store. To see which values
-          have support for a `_FILE` variant, run:
-          - `nix-instantiate --eval --expr '(import <nixpkgs> {}).healthchecks.secrets'`
-          - or `nix eval 'nixpkgs#healthchecks.secrets'` if the flake support has been enabled.
-      '';
-      type = types.submodule (settings: {
-        freeformType = types.attrsOf types.str;
-        options = {
-          ALLOWED_HOSTS = lib.mkOption {
-            type = types.listOf types.str;
-            default = [ "*" ];
-            description = "The host/domain names that this site can serve.";
-            apply = lib.concatStringsSep ",";
-          };
-
-          SECRET_KEY_FILE = mkOption {
-            type = types.path;
-            description = "Path to a file containing the secret key.";
-          };
-
-          DEBUG = mkOption {
-            type = types.bool;
-            default = false;
-            description = "Enable debug mode.";
-            apply = boolToPython;
-          };
-
-          REGISTRATION_OPEN = mkOption {
-            type = types.bool;
-            default = false;
-            description = ''
-              A boolean that controls whether site visitors can create new accounts.
-              Set it to false if you are setting up a private Healthchecks instance,
-              but it needs to be publicly accessible (so, for example, your cloud
-              services can send pings to it).
-              If you close new user registration, you can still selectively invite
-              users to your team account.
-            '';
-            apply = boolToPython;
-          };
-
-          DB = mkOption {
-            type = types.enum [ "sqlite" "postgres" "mysql" ];
-            default = "sqlite";
-            description = "Database engine to use.";
-          };
-
-          DB_NAME = mkOption {
-            type = types.str;
-            default =
-              if settings.config.DB == "sqlite"
-              then "${cfg.dataDir}/healthchecks.sqlite"
-              else "hc";
-            defaultText = lib.literalExpression ''
-              if config.${settings.options.DB} == "sqlite"
-              then "''${config.${opt.dataDir}}/healthchecks.sqlite"
-              else "hc"
-            '';
-            description = "Database name.";
-          };
-        };
-      });
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ healthchecksManageScript ];
-
-    systemd.targets.healthchecks = {
-      description = "Target for all Healthchecks services";
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network.target" "network-online.target" ];
-    };
-
-    systemd.services =
-      let
-        commonConfig = {
-          WorkingDirectory = cfg.dataDir;
-          User = cfg.user;
-          Group = cfg.group;
-          EnvironmentFile = [ environmentFile ];
-          StateDirectory = mkIf (cfg.dataDir == "/var/lib/healthchecks") "healthchecks";
-          StateDirectoryMode = mkIf (cfg.dataDir == "/var/lib/healthchecks") "0750";
-        };
-      in
-      {
-        healthchecks-migration = {
-          description = "Healthchecks migrations";
-          wantedBy = [ "healthchecks.target" ];
-
-          serviceConfig = commonConfig // {
-            Restart = "on-failure";
-            Type = "oneshot";
-            ExecStart = ''
-              ${pkg}/opt/healthchecks/manage.py migrate
-            '';
-          };
-        };
-
-        healthchecks = {
-          description = "Healthchecks WSGI Service";
-          wantedBy = [ "healthchecks.target" ];
-          after = [ "healthchecks-migration.service" ];
-
-          preStart = ''
-            ${pkg}/opt/healthchecks/manage.py collectstatic --no-input
-            ${pkg}/opt/healthchecks/manage.py remove_stale_contenttypes --no-input
-          '' + lib.optionalString (cfg.settings.DEBUG != "True") "${pkg}/opt/healthchecks/manage.py compress";
-
-          serviceConfig = commonConfig // {
-            Restart = "always";
-            ExecStart = ''
-              ${pkgs.python3Packages.gunicorn}/bin/gunicorn hc.wsgi \
-                --bind ${cfg.listenAddress}:${toString cfg.port} \
-                --pythonpath ${pkg}/opt/healthchecks
-            '';
-          };
-        };
-
-        healthchecks-sendalerts = {
-          description = "Healthchecks Alert Service";
-          wantedBy = [ "healthchecks.target" ];
-          after = [ "healthchecks.service" ];
-
-          serviceConfig = commonConfig // {
-            Restart = "always";
-            ExecStart = ''
-              ${pkg}/opt/healthchecks/manage.py sendalerts
-            '';
-          };
-        };
-
-        healthchecks-sendreports = {
-          description = "Healthchecks Reporting Service";
-          wantedBy = [ "healthchecks.target" ];
-          after = [ "healthchecks.service" ];
-
-          serviceConfig = commonConfig // {
-            Restart = "always";
-            ExecStart = ''
-              ${pkg}/opt/healthchecks/manage.py sendreports --loop
-            '';
-          };
-        };
-      };
-
-    users.users = optionalAttrs (cfg.user == defaultUser) {
-      ${defaultUser} =
-        {
-          description = "healthchecks service owner";
-          isSystemUser = true;
-          group = defaultUser;
-        };
-    };
-
-    users.groups = optionalAttrs (cfg.user == defaultUser) {
-      ${defaultUser} =
-        {
-          members = [ defaultUser ];
-        };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/hedgedoc.nix b/nixos/modules/services/web-apps/hedgedoc.nix
deleted file mode 100644
index 919d870b3a2c..000000000000
--- a/nixos/modules/services/web-apps/hedgedoc.nix
+++ /dev/null
@@ -1,321 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  inherit (lib) mkOption types literalExpression;
-
-  cfg = config.services.hedgedoc;
-
-  # 21.03 will not be an official release - it was instead 21.05.  This
-  # versionAtLeast statement remains set to 21.03 for backwards compatibility.
-  # See https://github.com/NixOS/nixpkgs/pull/108899 and
-  # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md.
-  name = if lib.versionAtLeast config.system.stateVersion "21.03" then
-    "hedgedoc"
-  else
-    "codimd";
-
-  settingsFormat = pkgs.formats.json { };
-in
-{
-  meta.maintainers = with lib.maintainers; [ SuperSandro2000 h7x4 ];
-
-  imports = [
-    (lib.mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ])
-    (lib.mkRenamedOptionModule [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ])
-    (lib.mkRenamedOptionModule [ "services" "hedgedoc" "groups" ] [ "users" "users" "hedgedoc" "extraGroups" ])
-    (lib.mkRemovedOptionModule [ "services" "hedgedoc" "workDir" ] ''
-      This option has been removed in favor of systemd managing the state directory.
-
-      If you have set this option without specifying `services.settings.uploadsDir`,
-      please move these files to `/var/lib/hedgedoc/uploads`, or set the option to point
-      at the correct location.
-    '')
-  ];
-
-  options.services.hedgedoc = {
-    package = lib.mkPackageOption pkgs "hedgedoc" { };
-    enable = lib.mkEnableOption "the HedgeDoc Markdown Editor";
-
-    settings = mkOption {
-      type = types.submodule {
-        freeformType = settingsFormat.type;
-        options = {
-          domain = mkOption {
-            type = with types; nullOr str;
-            default = null;
-            example = "hedgedoc.org";
-            description = ''
-              Domain to use for website.
-
-              This is useful if you are trying to run hedgedoc behind
-              a reverse proxy.
-            '';
-          };
-          urlPath = mkOption {
-            type = with types; nullOr str;
-            default = null;
-            example = "hedgedoc";
-            description = ''
-              URL path for the website.
-
-              This is useful if you are hosting hedgedoc on a path like
-              `www.example.com/hedgedoc`
-            '';
-          };
-          host = mkOption {
-            type = with types; nullOr str;
-            default = "localhost";
-            description = ''
-              Address to listen on.
-            '';
-          };
-          port = mkOption {
-            type = types.port;
-            default = 3000;
-            example = 80;
-            description = ''
-              Port to listen on.
-            '';
-          };
-          path = mkOption {
-            type = with types; nullOr path;
-            default = null;
-            example = "/run/hedgedoc/hedgedoc.sock";
-            description = ''
-              Path to UNIX domain socket to listen on
-
-              ::: {.note}
-                If specified, {option}`host` and {option}`port` will be ignored.
-              :::
-            '';
-          };
-          protocolUseSSL = mkOption {
-            type = types.bool;
-            default = false;
-            example = true;
-            description = ''
-              Use `https://` for all links.
-
-              This is useful if you are trying to run hedgedoc behind
-              a reverse proxy.
-
-              ::: {.note}
-                Only applied if {option}`domain` is set.
-              :::
-            '';
-          };
-          allowOrigin = mkOption {
-            type = with types; listOf str;
-            default = with cfg.settings; [ host ] ++ lib.optionals (domain != null) [ domain ];
-            defaultText = literalExpression ''
-              with config.services.hedgedoc.settings; [ host ] ++ lib.optionals (domain != null) [ domain ]
-            '';
-            example = [ "localhost" "hedgedoc.org" ];
-            description = ''
-              List of domains to whitelist.
-            '';
-          };
-          db = mkOption {
-            type = types.attrs;
-            default = {
-              dialect = "sqlite";
-              storage = "/var/lib/${name}/db.sqlite";
-            };
-            defaultText = literalExpression ''
-              {
-                dialect = "sqlite";
-                storage = "/var/lib/hedgedoc/db.sqlite";
-              }
-            '';
-            example = literalExpression ''
-              db = {
-                username = "hedgedoc";
-                database = "hedgedoc";
-                host = "localhost:5432";
-                # or via socket
-                # host = "/run/postgresql";
-                dialect = "postgresql";
-              };
-            '';
-            description = ''
-              Specify the configuration for sequelize.
-              HedgeDoc supports `mysql`, `postgres`, `sqlite` and `mssql`.
-              See <https://sequelize.readthedocs.io/en/v3/>
-              for more information.
-
-              ::: {.note}
-                The relevant parts will be overriden if you set {option}`dbURL`.
-              :::
-            '';
-          };
-          useSSL = mkOption {
-            type = types.bool;
-            default = false;
-            description = ''
-              Enable to use SSL server.
-
-              ::: {.note}
-                This will also enable {option}`protocolUseSSL`.
-
-                It will also require you to set the following:
-
-                - {option}`sslKeyPath`
-                - {option}`sslCertPath`
-                - {option}`sslCAPath`
-                - {option}`dhParamPath`
-              :::
-            '';
-          };
-          uploadsPath = mkOption {
-            type = types.path;
-            default = "/var/lib/${name}/uploads";
-            defaultText = "/var/lib/hedgedoc/uploads";
-            description = ''
-              Directory for storing uploaded images.
-            '';
-          };
-
-          # Declared because we change the default to false.
-          allowGravatar = mkOption {
-            type = types.bool;
-            default = false;
-            example = true;
-            description = ''
-              Whether to enable [Libravatar](https://wiki.libravatar.org/) as
-              profile picture source on your instance.
-
-              Despite the naming of the setting, Hedgedoc replaced Gravatar
-              with Libravatar in [CodiMD 1.4.0](https://hedgedoc.org/releases/1.4.0/)
-            '';
-          };
-        };
-      };
-
-      description = ''
-        HedgeDoc configuration, see
-        <https://docs.hedgedoc.org/configuration/>
-        for documentation.
-      '';
-    };
-
-    environmentFile = mkOption {
-      type = with types; nullOr path;
-      default = null;
-      example = "/var/lib/hedgedoc/hedgedoc.env";
-      description = ''
-        Environment file as defined in {manpage}`systemd.exec(5)`.
-
-        Secrets may be passed to the service without adding them to the world-readable
-        Nix store, by specifying placeholder variables as the option value in Nix and
-        setting these variables accordingly in the environment file.
-
-        ```
-          # snippet of HedgeDoc-related config
-          services.hedgedoc.settings.dbURL = "postgres://hedgedoc:\''${DB_PASSWORD}@db-host:5432/hedgedocdb";
-          services.hedgedoc.settings.minio.secretKey = "$MINIO_SECRET_KEY";
-        ```
-
-        ```
-          # content of the environment file
-          DB_PASSWORD=verysecretdbpassword
-          MINIO_SECRET_KEY=verysecretminiokey
-        ```
-
-        Note that this file needs to be available on the host on which
-        `HedgeDoc` is running.
-      '';
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    users.groups.${name} = { };
-    users.users.${name} = {
-      description = "HedgeDoc service user";
-      group = name;
-      isSystemUser = true;
-    };
-
-    services.hedgedoc.settings = {
-      defaultNotePath = lib.mkDefault "${cfg.package}/share/hedgedoc/public/default.md";
-      docsPath = lib.mkDefault "${cfg.package}/share/hedgedoc/public/docs";
-      viewPath = lib.mkDefault "${cfg.package}/share/hedgedoc/public/views";
-    };
-
-    systemd.services.hedgedoc = {
-      description = "HedgeDoc Service";
-      documentation = [ "https://docs.hedgedoc.org/" ];
-      wantedBy = [ "multi-user.target" ];
-      after = [ "networking.target" ];
-      preStart =
-        let
-          configFile = settingsFormat.generate "hedgedoc-config.json" {
-            production = cfg.settings;
-          };
-        in
-        ''
-          ${pkgs.envsubst}/bin/envsubst \
-            -o /run/${name}/config.json \
-            -i ${configFile}
-          ${pkgs.coreutils}/bin/mkdir -p ${cfg.settings.uploadsPath}
-        '';
-      serviceConfig = {
-        User = name;
-        Group = name;
-
-        Restart = "always";
-        ExecStart = lib.getExe cfg.package;
-        RuntimeDirectory = [ name ];
-        StateDirectory = [ name ];
-        WorkingDirectory = "/run/${name}";
-        ReadWritePaths = [
-          "-${cfg.settings.uploadsPath}"
-        ] ++ lib.optionals (cfg.settings.db ? "storage") [ "-${cfg.settings.db.storage}" ];
-        EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
-        Environment = [
-          "CMD_CONFIG_FILE=/run/${name}/config.json"
-          "NODE_ENV=production"
-        ];
-
-        # Hardening
-        AmbientCapabilities = "";
-        CapabilityBoundingSet = "";
-        LockPersonality = true;
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateMounts = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        ProcSubset = "pid";
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        ProtectSystem = "strict";
-        RemoveIPC = true;
-        RestrictAddressFamilies = [
-          "AF_INET"
-          "AF_INET6"
-          # Required for connecting to database sockets,
-          # and listening to unix socket at `cfg.settings.path`
-          "AF_UNIX"
-        ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SocketBindAllow = lib.mkIf (cfg.settings.path == null) cfg.settings.port;
-        SocketBindDeny = "any";
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [
-          "@system-service"
-          "~@privileged @obsolete"
-          "@pkey"
-        ];
-        UMask = "0007";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/hledger-web.nix b/nixos/modules/services/web-apps/hledger-web.nix
deleted file mode 100644
index 32d9df4e8458..000000000000
--- a/nixos/modules/services/web-apps/hledger-web.nix
+++ /dev/null
@@ -1,131 +0,0 @@
-{ lib, pkgs, config, ... }:
-with lib;
-let
-  cfg = config.services.hledger-web;
-in {
-  options.services.hledger-web = {
-
-    enable = mkEnableOption "hledger-web service";
-
-    serveApi = mkEnableOption "serving only the JSON web API, without the web UI";
-
-    host = mkOption {
-      type = types.str;
-      default = "127.0.0.1";
-      description = ''
-        Address to listen on.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5000;
-      example = 80;
-      description = ''
-        Port to listen on.
-      '';
-    };
-
-    allow = mkOption {
-      type = types.enum [ "view" "add" "edit" "sandstorm" ];
-      default = "view";
-      description = ''
-        User's access level for changing data.
-
-        * view: view only permission.
-        * add: view and add permissions.
-        * edit: view, add, and edit permissions.
-        * sandstorm: permissions from the `X-Sandstorm-Permissions` request header.
-      '';
-    };
-
-    stateDir = mkOption {
-      type = types.path;
-      default = "/var/lib/hledger-web";
-      description = ''
-        Path the service has access to. If left as the default value this
-        directory will automatically be created before the hledger-web server
-        starts, otherwise the sysadmin is responsible for ensuring the
-        directory exists with appropriate ownership and permissions.
-      '';
-    };
-
-    journalFiles = mkOption {
-      type = types.listOf types.str;
-      default = [ ".hledger.journal" ];
-      description = ''
-        Paths to journal files relative to {option}`services.hledger-web.stateDir`.
-      '';
-    };
-
-    baseUrl = mkOption {
-      type = with types; nullOr str;
-      default = null;
-      example = "https://example.org";
-      description = ''
-        Base URL, when sharing over a network.
-      '';
-    };
-
-    extraOptions = mkOption {
-      type = types.listOf types.str;
-      default = [];
-      example = [ "--forecast" ];
-      description = ''
-        Extra command line arguments to pass to hledger-web.
-      '';
-    };
-
-  };
-
-  imports = [
-    (mkRemovedOptionModule [ "services" "hledger-web" "capabilities" ]
-      "This option has been replaced by new option `services.hledger-web.allow`.")
-  ];
-
-  config = mkIf cfg.enable {
-
-    users.users.hledger = {
-      name = "hledger";
-      group = "hledger";
-      isSystemUser = true;
-      home = cfg.stateDir;
-      useDefaultShell = true;
-    };
-
-    users.groups.hledger = {};
-
-    systemd.services.hledger-web = let
-      serverArgs = with cfg; escapeShellArgs ([
-        "--serve"
-        "--host=${host}"
-        "--port=${toString port}"
-        "--allow=${allow}"
-        (optionalString (cfg.baseUrl != null) "--base-url=${cfg.baseUrl}")
-        (optionalString (cfg.serveApi) "--serve-api")
-      ] ++ (map (f: "--file=${stateDir}/${f}") cfg.journalFiles)
-        ++ extraOptions);
-    in {
-      description = "hledger-web - web-app for the hledger accounting tool.";
-      documentation = [ "https://hledger.org/hledger-web.html" ];
-      wantedBy = [ "multi-user.target" ];
-      after = [ "networking.target" ];
-      serviceConfig = mkMerge [
-        {
-          ExecStart = "${pkgs.hledger-web}/bin/hledger-web ${serverArgs}";
-          Restart = "always";
-          WorkingDirectory = cfg.stateDir;
-          User = "hledger";
-          Group = "hledger";
-          PrivateTmp = true;
-        }
-        (mkIf (cfg.stateDir == "/var/lib/hledger-web") {
-          StateDirectory = "hledger-web";
-        })
-      ];
-    };
-
-  };
-
-  meta.maintainers = with lib.maintainers; [ marijanp erictapen ];
-}
diff --git a/nixos/modules/services/web-apps/honk.md b/nixos/modules/services/web-apps/honk.md
deleted file mode 100644
index f34085f7dc52..000000000000
--- a/nixos/modules/services/web-apps/honk.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Honk {#module-services-honk}
-
-With Honk on NixOS you can quickly configure a complete ActivityPub server with
-minimal setup and support costs.
-
-## Basic usage {#module-services-honk-basic-usage}
-
-A minimal configuration looks like this:
-
-```nix
-{
-  services.honk = {
-    enable = true;
-    host = "0.0.0.0";
-    port = 8080;
-    username = "username";
-    passwordFile = "/etc/honk/password.txt";
-    servername = "honk.example.com";
-  };
-
-  networking.firewall.allowedTCPPorts = [ 8080 ];
-}
-```
diff --git a/nixos/modules/services/web-apps/honk.nix b/nixos/modules/services/web-apps/honk.nix
deleted file mode 100644
index e6a446192122..000000000000
--- a/nixos/modules/services/web-apps/honk.nix
+++ /dev/null
@@ -1,153 +0,0 @@
-{ config
-, lib
-, pkgs
-, ...
-}:
-let
-  cfg = config.services.honk;
-
-  honk-initdb-script = cfg: pkgs.writeShellApplication {
-    name = "honk-initdb-script";
-
-    runtimeInputs = with pkgs; [ coreutils ];
-
-    text = ''
-      PW=$(cat "$CREDENTIALS_DIRECTORY/honk_passwordFile")
-
-      echo -e "${cfg.username}\n''$PW\n${cfg.host}:${toString cfg.port}\n${cfg.servername}" | ${lib.getExe cfg.package} -datadir "$STATE_DIRECTORY" init
-    '';
-  };
-in
-{
-  options = {
-    services.honk = {
-      enable = lib.mkEnableOption "the Honk server";
-      package = lib.mkPackageOption pkgs "honk" { };
-
-      host = lib.mkOption {
-        default = "127.0.0.1";
-        description = ''
-          The host name or IP address the server should listen to.
-        '';
-        type = lib.types.str;
-      };
-
-      port = lib.mkOption {
-        default = 8080;
-        description = ''
-          The port the server should listen to.
-        '';
-        type = lib.types.port;
-      };
-
-      username = lib.mkOption {
-        description = ''
-          The admin account username.
-        '';
-        type = lib.types.str;
-      };
-
-      passwordFile = lib.mkOption {
-        description = ''
-          Password for admin account.
-          NOTE: Should be string not a store path, to prevent the password from being world readable
-        '';
-        type = lib.types.path;
-      };
-
-      servername = lib.mkOption {
-        description = ''
-          The server name.
-        '';
-        type = lib.types.str;
-      };
-
-      extraJS = lib.mkOption {
-        default = null;
-        description = ''
-          An extra JavaScript file to be loaded by the client.
-        '';
-        type = lib.types.nullOr lib.types.path;
-      };
-
-      extraCSS = lib.mkOption {
-        default = null;
-        description = ''
-          An extra CSS file to be loaded by the client.
-        '';
-        type = lib.types.nullOr lib.types.path;
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = cfg.username or "" != "";
-        message = ''
-          You have to define a username for Honk (`services.honk.username`).
-        '';
-      }
-      {
-        assertion = cfg.servername or "" != "";
-        message = ''
-          You have to define a servername for Honk (`services.honk.servername`).
-        '';
-      }
-    ];
-
-    systemd.services.honk-initdb = {
-      description = "Honk server database setup";
-      requiredBy = [ "honk.service" ];
-      before = [ "honk.service" ];
-
-      serviceConfig = {
-        LoadCredential = [
-          "honk_passwordFile:${cfg.passwordFile}"
-        ];
-        Type = "oneshot";
-        StateDirectory = "honk";
-        DynamicUser = true;
-        RemainAfterExit = true;
-        ExecStart = lib.getExe (honk-initdb-script cfg);
-        PrivateTmp = true;
-      };
-
-      unitConfig = {
-        ConditionPathExists = [
-          # Skip this service if the database already exists
-          "!%S/honk/honk.db"
-        ];
-      };
-    };
-
-    systemd.services.honk = {
-      description = "Honk server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-      bindsTo = [ "honk-initdb.service" ];
-      preStart = ''
-        mkdir -p $STATE_DIRECTORY/views
-        ${lib.optionalString (cfg.extraJS != null) "ln -fs ${cfg.extraJS} $STATE_DIRECTORY/views/local.js"}
-        ${lib.optionalString (cfg.extraCSS != null) "ln -fs ${cfg.extraCSS} $STATE_DIRECTORY/views/local.css"}
-        ${lib.getExe cfg.package} -datadir $STATE_DIRECTORY -viewdir ${cfg.package}/share/honk backup $STATE_DIRECTORY/backup
-        ${lib.getExe cfg.package} -datadir $STATE_DIRECTORY -viewdir ${cfg.package}/share/honk upgrade
-        ${lib.getExe cfg.package} -datadir $STATE_DIRECTORY -viewdir ${cfg.package}/share/honk cleanup
-      '';
-      serviceConfig = {
-        ExecStart = ''
-          ${lib.getExe cfg.package} -datadir $STATE_DIRECTORY -viewdir ${cfg.package}/share/honk
-        '';
-        StateDirectory = "honk";
-        DynamicUser = true;
-        PrivateTmp = "yes";
-        Restart = "on-failure";
-      };
-    };
-  };
-
-  meta = {
-    maintainers = with lib.maintainers; [ drupol ];
-    doc = ./honk.md;
-  };
-}
diff --git a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
deleted file mode 100644
index b9761061aaae..000000000000
--- a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
+++ /dev/null
@@ -1,262 +0,0 @@
-{ config, lib, pkgs, ... }: with lib; let
-  cfg = config.services.icingaweb2;
-  fpm = config.services.phpfpm.pools.${poolName};
-  poolName = "icingaweb2";
-
-  defaultConfig = {
-    global = {
-      module_path = "${pkgs.icingaweb2}/modules";
-    };
-  };
-in {
-  meta.maintainers = with maintainers; [ das_j ];
-
-  options.services.icingaweb2 = with types; {
-    enable = mkEnableOption "the icingaweb2 web interface";
-
-    pool = mkOption {
-      type = str;
-      default = poolName;
-      description = ''
-         Name of existing PHP-FPM pool that is used to run Icingaweb2.
-         If not specified, a pool will automatically created with default values.
-      '';
-    };
-
-    libraryPaths = mkOption {
-      type = attrsOf package;
-      default = { };
-      description = ''
-        Libraries to add to the Icingaweb2 library path.
-        The name of the attribute is the name of the library, the value
-        is the package to add.
-      '';
-    };
-
-    virtualHost = mkOption {
-      type = nullOr str;
-      default = "icingaweb2";
-      description = ''
-        Name of the nginx virtualhost to use and setup. If null, no virtualhost is set up.
-      '';
-    };
-
-    timezone = mkOption {
-      type = str;
-      default = "UTC";
-      example = "Europe/Berlin";
-      description = "PHP-compliant timezone specification";
-    };
-
-    modules = {
-      doc.enable = mkEnableOption "the icingaweb2 doc module";
-      migrate.enable = mkEnableOption "the icingaweb2 migrate module";
-      setup.enable = mkEnableOption "the icingaweb2 setup module";
-      test.enable = mkEnableOption "the icingaweb2 test module";
-      translation.enable = mkEnableOption "the icingaweb2 translation module";
-    };
-
-    modulePackages = mkOption {
-      type = attrsOf package;
-      default = {};
-      example = literalExpression ''
-        {
-          "snow" = icingaweb2Modules.theme-snow;
-        }
-      '';
-      description = ''
-        Name-package attrset of Icingaweb 2 modules packages to enable.
-
-        If you enable modules manually (e.g. via the web ui), they will not be touched.
-      '';
-    };
-
-    generalConfig = mkOption {
-      type = nullOr attrs;
-      default = null;
-      example = {
-        general = {
-          showStacktraces = 1;
-          config_resource = "icingaweb_db";
-        };
-        logging = {
-          log = "syslog";
-          level = "CRITICAL";
-        };
-      };
-      description = ''
-        config.ini contents.
-        Will automatically be converted to a .ini file.
-        If you don't set global.module_path, the module will take care of it.
-
-        If the value is null, no config.ini is created and you can
-        modify it manually (e.g. via the web interface).
-        Note that you need to update module_path manually.
-      '';
-    };
-
-    resources = mkOption {
-      type = nullOr attrs;
-      default = null;
-      example = {
-        icingaweb_db = {
-          type = "db";
-          db = "mysql";
-          host = "localhost";
-          username = "icingaweb2";
-          password = "icingaweb2";
-          dbname = "icingaweb2";
-        };
-      };
-      description = ''
-        resources.ini contents.
-        Will automatically be converted to a .ini file.
-
-        If the value is null, no resources.ini is created and you can
-        modify it manually (e.g. via the web interface).
-        Note that if you set passwords here, they will go into the nix store.
-      '';
-    };
-
-    authentications = mkOption {
-      type = nullOr attrs;
-      default = null;
-      example = {
-        icingaweb = {
-          backend = "db";
-          resource = "icingaweb_db";
-        };
-      };
-      description = ''
-        authentication.ini contents.
-        Will automatically be converted to a .ini file.
-
-        If the value is null, no authentication.ini is created and you can
-        modify it manually (e.g. via the web interface).
-      '';
-    };
-
-    groupBackends = mkOption {
-      type = nullOr attrs;
-      default = null;
-      example = {
-        icingaweb = {
-          backend = "db";
-          resource = "icingaweb_db";
-        };
-      };
-      description = ''
-        groups.ini contents.
-        Will automatically be converted to a .ini file.
-
-        If the value is null, no groups.ini is created and you can
-        modify it manually (e.g. via the web interface).
-      '';
-    };
-
-    roles = mkOption {
-      type = nullOr attrs;
-      default = null;
-      example = {
-        Administrators = {
-          users = "admin";
-          permissions = "*";
-        };
-      };
-      description = ''
-        roles.ini contents.
-        Will automatically be converted to a .ini file.
-
-        If the value is null, no roles.ini is created and you can
-        modify it manually (e.g. via the web interface).
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
-      ${poolName} = {
-        user = "icingaweb2";
-        phpEnv = {
-          ICINGAWEB_LIBDIR = toString (pkgs.linkFarm "icingaweb2-libdir" (mapAttrsToList (name: path: { inherit name path; }) cfg.libraryPaths));
-        };
-        phpPackage = pkgs.php.withExtensions ({ enabled, all }: [ all.imagick ] ++ enabled);
-        phpOptions = ''
-          date.timezone = "${cfg.timezone}"
-        '';
-        settings = mapAttrs (name: mkDefault) {
-          "listen.owner" = "nginx";
-          "listen.group" = "nginx";
-          "listen.mode" = "0600";
-          "pm" = "dynamic";
-          "pm.max_children" = 75;
-          "pm.start_servers" = 2;
-          "pm.min_spare_servers" = 2;
-          "pm.max_spare_servers" = 10;
-        };
-      };
-    };
-
-    services.icingaweb2.libraryPaths = {
-      ipl = pkgs.icingaweb2-ipl;
-      thirdparty = pkgs.icingaweb2-thirdparty;
-    };
-
-    systemd.services."phpfpm-${poolName}".serviceConfig.ReadWritePaths = [ "/etc/icingaweb2" ];
-
-    services.nginx = {
-      enable = true;
-      virtualHosts = mkIf (cfg.virtualHost != null) {
-        ${cfg.virtualHost} = {
-          root = "${pkgs.icingaweb2}/public";
-
-          extraConfig = ''
-            index index.php;
-            try_files $1 $uri $uri/ /index.php$is_args$args;
-          '';
-
-          locations."~ ..*/.*.php$".extraConfig = ''
-            return 403;
-          '';
-
-          locations."~ ^/index.php(.*)$".extraConfig = ''
-            fastcgi_intercept_errors on;
-            fastcgi_index index.php;
-            include ${config.services.nginx.package}/conf/fastcgi.conf;
-            try_files $uri =404;
-            fastcgi_split_path_info ^(.+\.php)(/.+)$;
-            fastcgi_pass unix:${fpm.socket};
-            fastcgi_param SCRIPT_FILENAME ${pkgs.icingaweb2}/public/index.php;
-          '';
-        };
-      };
-    };
-
-    # /etc/icingaweb2
-    environment.etc = let
-      doModule = name: optionalAttrs (cfg.modules.${name}.enable) { "icingaweb2/enabledModules/${name}".source = "${pkgs.icingaweb2}/modules/${name}"; };
-    in {}
-      # Module packages
-      // (mapAttrs' (k: v: nameValuePair "icingaweb2/enabledModules/${k}" { source = v; }) cfg.modulePackages)
-      # Built-in modules
-      // doModule "doc"
-      // doModule "migrate"
-      // doModule "setup"
-      // doModule "test"
-      // doModule "translation"
-      # Configs
-      // optionalAttrs (cfg.generalConfig != null) { "icingaweb2/config.ini".text = generators.toINI {} (defaultConfig // cfg.generalConfig); }
-      // optionalAttrs (cfg.resources != null) { "icingaweb2/resources.ini".text = generators.toINI {} cfg.resources; }
-      // optionalAttrs (cfg.authentications != null) { "icingaweb2/authentication.ini".text = generators.toINI {} cfg.authentications; }
-      // optionalAttrs (cfg.groupBackends != null) { "icingaweb2/groups.ini".text = generators.toINI {} cfg.groupBackends; }
-      // optionalAttrs (cfg.roles != null) { "icingaweb2/roles.ini".text = generators.toINI {} cfg.roles; };
-
-    # User and group
-    users.groups.icingaweb2 = {};
-    users.users.icingaweb2 = {
-      description = "Icingaweb2 service user";
-      group = "icingaweb2";
-      isSystemUser = true;
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix b/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
deleted file mode 100644
index e9c1d4ffe5ea..000000000000
--- a/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
+++ /dev/null
@@ -1,157 +0,0 @@
-{ config, lib, pkgs, ... }: with lib; let
-  cfg = config.services.icingaweb2.modules.monitoring;
-
-  configIni = ''
-    [security]
-    protected_customvars = "${concatStringsSep "," cfg.generalConfig.protectedVars}"
-  '';
-
-  backendsIni = let
-    formatBool = b: if b then "1" else "0";
-  in concatStringsSep "\n" (mapAttrsToList (name: config: ''
-    [${name}]
-    type = "ido"
-    resource = "${config.resource}"
-    disabled = "${formatBool config.disabled}"
-  '') cfg.backends);
-
-  transportsIni = concatStringsSep "\n" (mapAttrsToList (name: config: ''
-    [${name}]
-    type = "${config.type}"
-    ${optionalString (config.instance != null) ''instance = "${config.instance}"''}
-    ${optionalString (config.type == "local" || config.type == "remote") ''path = "${config.path}"''}
-    ${optionalString (config.type != "local") ''
-      host = "${config.host}"
-      ${optionalString (config.port != null) ''port = "${toString config.port}"''}
-      user${optionalString (config.type == "api") "name"} = "${config.username}"
-    ''}
-    ${optionalString (config.type == "api") ''password = "${config.password}"''}
-    ${optionalString (config.type == "remote") ''resource = "${config.resource}"''}
-  '') cfg.transports);
-
-in {
-  options.services.icingaweb2.modules.monitoring = with types; {
-    enable = mkOption {
-      type = bool;
-      default = true;
-      description = "Whether to enable the icingaweb2 monitoring module.";
-    };
-
-    generalConfig = {
-      mutable = mkOption {
-        type = bool;
-        default = false;
-        description = "Make config.ini of the monitoring module mutable (e.g. via the web interface).";
-      };
-
-      protectedVars = mkOption {
-        type = listOf str;
-        default = [ "*pw*" "*pass*" "community" ];
-        description = "List of string patterns for custom variables which should be excluded from user’s view.";
-      };
-    };
-
-    mutableBackends = mkOption {
-      type = bool;
-      default = false;
-      description = "Make backends.ini of the monitoring module mutable (e.g. via the web interface).";
-    };
-
-    backends = mkOption {
-      default = { icinga = { resource = "icinga_ido"; }; };
-      description = "Monitoring backends to define";
-      type = attrsOf (submodule ({ name, ... }: {
-        options = {
-          name = mkOption {
-            visible = false;
-            default = name;
-            type = str;
-            description = "Name of this backend";
-          };
-
-          resource = mkOption {
-            type = str;
-            description = "Name of the IDO resource";
-          };
-
-          disabled = mkOption {
-            type = bool;
-            default = false;
-            description = "Disable this backend";
-          };
-        };
-      }));
-    };
-
-    mutableTransports = mkOption {
-      type = bool;
-      default = true;
-      description = "Make commandtransports.ini of the monitoring module mutable (e.g. via the web interface).";
-    };
-
-    transports = mkOption {
-      default = {};
-      description = "Command transports to define";
-      type = attrsOf (submodule ({ name, ... }: {
-        options = {
-          name = mkOption {
-            visible = false;
-            default = name;
-            type = str;
-            description = "Name of this transport";
-          };
-
-          type = mkOption {
-            type = enum [ "api" "local" "remote" ];
-            default = "api";
-            description = "Type of  this transport";
-          };
-
-          instance = mkOption {
-            type = nullOr str;
-            default = null;
-            description = "Assign a icinga instance to this transport";
-          };
-
-          path = mkOption {
-            type = str;
-            description = "Path to the socket for local or remote transports";
-          };
-
-          host = mkOption {
-            type = str;
-            description = "Host for the api or remote transport";
-          };
-
-          port = mkOption {
-            type = nullOr str;
-            default = null;
-            description = "Port to connect to for the api or remote transport";
-          };
-
-          username = mkOption {
-            type = str;
-            description = "Username for the api or remote transport";
-          };
-
-          password = mkOption {
-            type = str;
-            description = "Password for the api transport";
-          };
-
-          resource = mkOption {
-            type = str;
-            description = "SSH identity resource for the remote transport";
-          };
-        };
-      }));
-    };
-  };
-
-  config = mkIf (config.services.icingaweb2.enable && cfg.enable) {
-    environment.etc = { "icingaweb2/enabledModules/monitoring" = { source = "${pkgs.icingaweb2}/modules/monitoring"; }; }
-      // optionalAttrs (!cfg.generalConfig.mutable) { "icingaweb2/modules/monitoring/config.ini".text = configIni; }
-      // optionalAttrs (!cfg.mutableBackends) { "icingaweb2/modules/monitoring/backends.ini".text = backendsIni; }
-      // optionalAttrs (!cfg.mutableTransports) { "icingaweb2/modules/monitoring/commandtransports.ini".text = transportsIni; };
-  };
-}
diff --git a/nixos/modules/services/web-apps/invidious.nix b/nixos/modules/services/web-apps/invidious.nix
deleted file mode 100644
index f0e860383a62..000000000000
--- a/nixos/modules/services/web-apps/invidious.nix
+++ /dev/null
@@ -1,403 +0,0 @@
-{ lib, config, pkgs, options, ... }:
-let
-  cfg = config.services.invidious;
-  # To allow injecting secrets with jq, json (instead of yaml) is used
-  settingsFormat = pkgs.formats.json { };
-  inherit (lib) types;
-
-  settingsFile = settingsFormat.generate "invidious-settings" cfg.settings;
-
-  generatedHmacKeyFile = "/var/lib/invidious/hmac_key";
-  generateHmac = cfg.hmacKeyFile == null;
-
-  commonInvidousServiceConfig = {
-    description = "Invidious (An alternative YouTube front-end)";
-    wants = [ "network-online.target" ];
-    after = [ "network-online.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
-    requires = lib.optional cfg.database.createLocally "postgresql.service";
-    wantedBy = [ "multi-user.target" ];
-
-    serviceConfig = {
-      RestartSec = "2s";
-      DynamicUser = true;
-      User = lib.mkIf (cfg.database.createLocally || cfg.serviceScale > 1) "invidious";
-      StateDirectory = "invidious";
-      StateDirectoryMode = "0750";
-
-      CapabilityBoundingSet = "";
-      PrivateDevices = true;
-      PrivateUsers = true;
-      ProtectHome = true;
-      ProtectKernelLogs = true;
-      ProtectProc = "invisible";
-      RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
-      RestrictNamespaces = true;
-      SystemCallArchitectures = "native";
-      SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
-
-      # Because of various issues Invidious must be restarted often, at least once a day, ideally
-      # every hour.
-      # This option enables the automatic restarting of the Invidious instance.
-      # To ensure multiple instances of Invidious are not restarted at the exact same time, a
-      # randomized extra offset of up to 5 minutes is added.
-      Restart = lib.mkDefault "always";
-      RuntimeMaxSec = lib.mkDefault "1h";
-      RuntimeRandomizedExtraSec = lib.mkDefault "5min";
-    };
-  };
-  mkInvidiousService = scaleIndex:
-    lib.foldl' lib.recursiveUpdate commonInvidousServiceConfig [
-      # only generate the hmac file in the first service
-      (lib.optionalAttrs (scaleIndex == 0) {
-        preStart = lib.optionalString generateHmac ''
-          if [[ ! -e "${generatedHmacKeyFile}" ]]; then
-            ${pkgs.pwgen}/bin/pwgen 20 1 > "${generatedHmacKeyFile}"
-            chmod 0600 "${generatedHmacKeyFile}"
-          fi
-        '';
-      })
-      # configure the secondary services to run after the first service
-      (lib.optionalAttrs (scaleIndex > 0) {
-        after = commonInvidousServiceConfig.after ++ [ "invidious.service" ];
-        wants = commonInvidousServiceConfig.wants ++ [ "invidious.service" ];
-      })
-      {
-        script = ''
-          configParts=()
-        ''
-        # autogenerated hmac_key
-        + lib.optionalString generateHmac ''
-          configParts+=("$(${pkgs.jq}/bin/jq -R '{"hmac_key":.}' <"${generatedHmacKeyFile}")")
-        ''
-        # generated settings file
-        + ''
-          configParts+=("$(< ${lib.escapeShellArg settingsFile})")
-        ''
-        # optional database password file
-        + lib.optionalString (cfg.database.host != null) ''
-          configParts+=("$(${pkgs.jq}/bin/jq -R '{"db":{"password":.}}' ${lib.escapeShellArg cfg.database.passwordFile})")
-        ''
-        # optional extra settings file
-        + lib.optionalString (cfg.extraSettingsFile != null) ''
-          configParts+=("$(< ${lib.escapeShellArg cfg.extraSettingsFile})")
-        ''
-        # explicitly specified hmac key file
-        + lib.optionalString (cfg.hmacKeyFile != null) ''
-          configParts+=("$(< ${lib.escapeShellArg cfg.hmacKeyFile})")
-        ''
-        # configure threads for secondary instances
-        + lib.optionalString (scaleIndex > 0) ''
-          configParts+=('{"channel_threads":0, "feed_threads":0}')
-        ''
-        # configure different ports for the instances
-        + ''
-          configParts+=('{"port":${toString (cfg.port + scaleIndex)}}')
-        ''
-        # merge all parts into a single configuration with later elements overriding previous elements
-        + ''
-          export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s 'reduce .[] as $item ({}; . * $item)' <<<"''${configParts[*]}")"
-          exec ${cfg.package}/bin/invidious
-        '';
-      }
-    ];
-
-  serviceConfig = {
-    systemd.services = builtins.listToAttrs (builtins.genList
-      (scaleIndex: {
-        name = "invidious" + lib.optionalString (scaleIndex > 0) "-${builtins.toString scaleIndex}";
-        value = mkInvidiousService scaleIndex;
-      })
-      cfg.serviceScale);
-
-    services.invidious.settings = {
-      # Automatically initialises and migrates the database if necessary
-      check_tables = true;
-
-      db = {
-        user = lib.mkDefault (
-          if (lib.versionAtLeast config.system.stateVersion "24.05")
-          then "invidious"
-          else "kemal"
-        );
-        dbname = lib.mkDefault "invidious";
-        port = cfg.database.port;
-        # Blank for unix sockets, see
-        # https://github.com/will/crystal-pg/blob/1548bb255210/src/pq/conninfo.cr#L100-L108
-        host = lib.optionalString (cfg.database.host != null) cfg.database.host;
-        # Not needed because peer authentication is enabled
-        password = lib.mkIf (cfg.database.host == null) "";
-      };
-
-      host_binding = cfg.address;
-    } // (lib.optionalAttrs (cfg.domain != null) {
-      inherit (cfg) domain;
-    });
-
-    assertions = [
-      {
-        assertion = cfg.database.host != null -> cfg.database.passwordFile != null;
-        message = "If database host isn't null, database password needs to be set";
-      }
-      {
-        assertion = cfg.serviceScale >= 1;
-        message = "Service can't be scaled below one instance";
-      }
-    ];
-  };
-
-  # Settings necessary for running with an automatically managed local database
-  localDatabaseConfig = lib.mkIf cfg.database.createLocally {
-    assertions = [
-      {
-        assertion = cfg.settings.db.user == cfg.settings.db.dbname;
-        message = ''
-          For local automatic database provisioning (services.invidious.database.createLocally == true)
-          to  work, the username used to connect to PostgreSQL must match the database name, that is
-          services.invidious.settings.db.user must match services.invidious.settings.db.dbname.
-          This is the default since NixOS 24.05. For older systems, it is normally safe to manually set
-          the user to "invidious" as the new user will be created with permissions
-          for the existing database. `REASSIGN OWNED BY kemal TO invidious;` may also be needed, it can be
-          run as `sudo -u postgres env psql --user=postgres --dbname=invidious -c 'reassign OWNED BY kemal to invidious;'`.
-        '';
-      }
-    ];
-    # Default to using the local database if we create it
-    services.invidious.database.host = lib.mkDefault null;
-
-    services.postgresql = {
-      enable = true;
-      ensureUsers = lib.singleton { name = cfg.settings.db.user; ensureDBOwnership = true; };
-      ensureDatabases = lib.singleton cfg.settings.db.dbname;
-    };
-  };
-
-  ytproxyConfig = lib.mkIf cfg.http3-ytproxy.enable {
-    systemd.services.http3-ytproxy = {
-      description = "HTTP3 ytproxy for Invidious";
-      wants = [ "network-online.target" ];
-      after = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      script = ''
-        mkdir -p socket
-        exec ${lib.getExe cfg.http3-ytproxy.package};
-      '';
-
-      serviceConfig = {
-        RestartSec = "2s";
-        DynamicUser = true;
-        User = lib.mkIf cfg.nginx.enable config.services.nginx.user;
-        RuntimeDirectory = "http3-ytproxy";
-        WorkingDirectory = "/run/http3-ytproxy";
-      };
-    };
-
-    services.nginx.virtualHosts.${cfg.domain} = lib.mkIf cfg.nginx.enable {
-      locations."~ (^/videoplayback|^/vi/|^/ggpht/|^/sb/)" = {
-        proxyPass = "http://unix:/run/http3-ytproxy/socket/http-proxy.sock";
-      };
-    };
-  };
-
-  nginxConfig = lib.mkIf cfg.nginx.enable {
-    services.invidious.settings = {
-      https_only = config.services.nginx.virtualHosts.${cfg.domain}.forceSSL;
-      external_port = 80;
-    };
-
-    services.nginx = let
-      ip = if cfg.address == "0.0.0.0" then "127.0.0.1" else cfg.address;
-    in
-    {
-      enable = true;
-      virtualHosts.${cfg.domain} = {
-        locations."/".proxyPass =
-          if cfg.serviceScale == 1 then
-            "http://${ip}:${toString cfg.port}"
-          else "http://upstream-invidious";
-
-        enableACME = lib.mkDefault true;
-        forceSSL = lib.mkDefault true;
-      };
-      upstreams = lib.mkIf (cfg.serviceScale > 1) {
-        "upstream-invidious".servers = builtins.listToAttrs (builtins.genList
-          (scaleIndex: {
-            name = "${ip}:${toString (cfg.port + scaleIndex)}";
-            value = { };
-          })
-          cfg.serviceScale);
-      };
-    };
-
-    assertions = [{
-      assertion = cfg.domain != null;
-      message = "To use services.invidious.nginx, you need to set services.invidious.domain";
-    }];
-  };
-in
-{
-  options.services.invidious = {
-    enable = lib.mkEnableOption "Invidious";
-
-    package = lib.mkPackageOption pkgs "invidious" { };
-
-    settings = lib.mkOption {
-      type = settingsFormat.type;
-      default = { };
-      description = ''
-        The settings Invidious should use.
-
-        See [config.example.yml](https://github.com/iv-org/invidious/blob/master/config/config.example.yml) for a list of all possible options.
-      '';
-    };
-
-    hmacKeyFile = lib.mkOption {
-      type = types.nullOr types.path;
-      default = null;
-      description = ''
-        A path to a file containing the `hmac_key`. If `null`, a key will be generated automatically on first
-        start.
-
-        If non-`null`, this option overrides any `hmac_key` specified in {option}`services.invidious.settings` or
-        via {option}`services.invidious.extraSettingsFile`.
-      '';
-    };
-
-    extraSettingsFile = lib.mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      description = ''
-        A file including Invidious settings.
-
-        It gets merged with the settings specified in {option}`services.invidious.settings`
-        and can be used to store secrets like `hmac_key` outside of the nix store.
-      '';
-    };
-
-    serviceScale = lib.mkOption {
-      type = types.int;
-      default = 1;
-      description = ''
-        How many invidious instances to run.
-
-        See https://docs.invidious.io/improve-public-instance/#2-multiple-invidious-processes for more details
-        on how this is intended to work. All instances beyond the first one have the options `channel_threads`
-        and `feed_threads` set to 0 to avoid conflicts with multiple instances refreshing subscriptions. Instances
-        will be configured to bind to consecutive ports starting with {option}`services.invidious.port` for the
-        first instance.
-      '';
-    };
-
-    # This needs to be outside of settings to avoid infinite recursion
-    # (determining if nginx should be enabled and therefore the settings
-    # modified).
-    domain = lib.mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      description = ''
-        The FQDN Invidious is reachable on.
-
-        This is used to configure nginx and for building absolute URLs.
-      '';
-    };
-
-    address = lib.mkOption {
-      type = types.str;
-      # default from https://github.com/iv-org/invidious/blob/master/config/config.example.yml
-      default = if cfg.nginx.enable then "127.0.0.1" else "0.0.0.0";
-      defaultText = lib.literalExpression ''if config.services.invidious.nginx.enable then "127.0.0.1" else "0.0.0.0"'';
-      description = ''
-        The IP address Invidious should bind to.
-      '';
-    };
-
-    port = lib.mkOption {
-      type = types.port;
-      # Default from https://docs.invidious.io/Configuration.md
-      default = 3000;
-      description = ''
-        The port Invidious should listen on.
-
-        To allow access from outside,
-        you can use either {option}`services.invidious.nginx`
-        or add `config.services.invidious.port` to {option}`networking.firewall.allowedTCPPorts`.
-      '';
-    };
-
-    database = {
-      createLocally = lib.mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether to create a local database with PostgreSQL.
-        '';
-      };
-
-      host = lib.mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          The database host Invidious should use.
-
-          If `null`, the local unix socket is used. Otherwise
-          TCP is used.
-        '';
-      };
-
-      port = lib.mkOption {
-        type = types.port;
-        default = config.services.postgresql.settings.port;
-        defaultText = lib.literalExpression "config.services.postgresql.settings.port";
-        description = ''
-          The port of the database Invidious should use.
-
-          Defaults to the the default postgresql port.
-        '';
-      };
-
-      passwordFile = lib.mkOption {
-        type = types.nullOr types.str;
-        apply = lib.mapNullable toString;
-        default = null;
-        description = ''
-          Path to file containing the database password.
-        '';
-      };
-    };
-
-    nginx.enable = lib.mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Whether to configure nginx as a reverse proxy for Invidious.
-
-        It serves it under the domain specified in {option}`services.invidious.settings.domain` with enabled TLS and ACME.
-        Further configuration can be done through {option}`services.nginx.virtualHosts.''${config.services.invidious.settings.domain}.*`,
-        which can also be used to disable AMCE and TLS.
-      '';
-    };
-
-    http3-ytproxy = {
-      enable = lib.mkOption {
-        type = lib.types.bool;
-        default = false;
-        description = ''
-          Whether to enable http3-ytproxy for faster loading of images and video playback.
-
-          If {option}`services.invidious.nginx.enable` is used, nginx will be configured automatically. If not, you
-          need to configure a reverse proxy yourself according to
-          https://docs.invidious.io/improve-public-instance/#3-speed-up-video-playback-with-http3-ytproxy.
-        '';
-      };
-
-      package = lib.mkPackageOptionMD pkgs "http3-ytproxy" { };
-    };
-  };
-
-  config = lib.mkIf cfg.enable (lib.mkMerge [
-    serviceConfig
-    localDatabaseConfig
-    nginxConfig
-    ytproxyConfig
-  ]);
-}
diff --git a/nixos/modules/services/web-apps/invoiceplane.nix b/nixos/modules/services/web-apps/invoiceplane.nix
deleted file mode 100644
index 9a9f180b2102..000000000000
--- a/nixos/modules/services/web-apps/invoiceplane.nix
+++ /dev/null
@@ -1,400 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.services.invoiceplane;
-  eachSite = cfg.sites;
-  user = "invoiceplane";
-  webserver = config.services.${cfg.webserver};
-
-  invoiceplane-config = hostName: cfg: pkgs.writeText "ipconfig.php" ''
-    IP_URL=http://${hostName}
-    ENABLE_DEBUG=false
-    DISABLE_SETUP=false
-    REMOVE_INDEXPHP=false
-    DB_HOSTNAME=${cfg.database.host}
-    DB_USERNAME=${cfg.database.user}
-    # NOTE: file_get_contents adds newline at the end of returned string
-    DB_PASSWORD=${optionalString (cfg.database.passwordFile != null) "trim(file_get_contents('${cfg.database.passwordFile}'), \"\\r\\n\")"}
-    DB_DATABASE=${cfg.database.name}
-    DB_PORT=${toString cfg.database.port}
-    SESS_EXPIRATION=864000
-    ENABLE_INVOICE_DELETION=false
-    DISABLE_READ_ONLY=false
-    ENCRYPTION_KEY=
-    ENCRYPTION_CIPHER=AES-256
-    SETUP_COMPLETED=false
-    REMOVE_INDEXPHP=true
-  '';
-
-  mkPhpValue = v:
-    if isString v then escapeShellArg v
-    # NOTE: If any value contains a , (comma) this will not get escaped
-    else if isList v && any lib.strings.isCoercibleToString v then escapeShellArg (concatMapStringsSep "," toString v)
-    else if isInt v then toString v
-    else if isBool v then boolToString v
-    else abort "The Invoiceplane config value ${lib.generators.toPretty {} v} can not be encoded."
-  ;
-
-  extraConfig = hostName: cfg: let
-    settings = mapAttrsToList (k: v: "${k}=${mkPhpValue v}") cfg.settings;
-  in pkgs.writeText "extraConfig.php" (concatStringsSep "\n" settings);
-
-  pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
-    pname = "invoiceplane-${hostName}";
-    version = src.version;
-    src = pkgs.invoiceplane;
-
-    postPatch = ''
-      # Patch index.php file to load additional config file
-      substituteInPlace index.php \
-        --replace-fail "require('vendor/autoload.php');" "require('vendor/autoload.php'); \$dotenv = Dotenv\Dotenv::createImmutable(__DIR__, 'extraConfig.php'); \$dotenv->load();";
-    '';
-
-    installPhase = ''
-      mkdir -p $out
-      cp -r * $out/
-
-      # symlink uploads and log directories
-      rm -r $out/uploads $out/application/logs $out/vendor/mpdf/mpdf/tmp
-      ln -sf ${cfg.stateDir}/uploads $out/
-      ln -sf ${cfg.stateDir}/logs $out/application/
-      ln -sf ${cfg.stateDir}/tmp $out/vendor/mpdf/mpdf/
-
-      # symlink the InvoicePlane config
-      ln -s ${cfg.stateDir}/ipconfig.php $out/ipconfig.php
-
-      # symlink the extraConfig file
-      ln -s ${extraConfig hostName cfg} $out/extraConfig.php
-
-      # symlink additional templates
-      ${concatMapStringsSep "\n" (template: "cp -r ${template}/. $out/application/views/invoice_templates/pdf/") cfg.invoiceTemplates}
-    '';
-  };
-
-  siteOpts = { lib, name, ... }:
-    {
-      options = {
-
-        enable = mkEnableOption "InvoicePlane web application";
-
-        stateDir = mkOption {
-          type = types.path;
-          default = "/var/lib/invoiceplane/${name}";
-          description = ''
-            This directory is used for uploads of attachments and cache.
-            The directory passed here is automatically created and permissions
-            adjusted as required.
-          '';
-        };
-
-        database = {
-          host = mkOption {
-            type = types.str;
-            default = "localhost";
-            description = "Database host address.";
-          };
-
-          port = mkOption {
-            type = types.port;
-            default = 3306;
-            description = "Database host port.";
-          };
-
-          name = mkOption {
-            type = types.str;
-            default = "invoiceplane";
-            description = "Database name.";
-          };
-
-          user = mkOption {
-            type = types.str;
-            default = "invoiceplane";
-            description = "Database user.";
-          };
-
-          passwordFile = mkOption {
-            type = types.nullOr types.path;
-            default = null;
-            example = "/run/keys/invoiceplane-dbpassword";
-            description = ''
-              A file containing the password corresponding to
-              {option}`database.user`.
-            '';
-          };
-
-          createLocally = mkOption {
-            type = types.bool;
-            default = true;
-            description = "Create the database and database user locally.";
-          };
-        };
-
-        invoiceTemplates = mkOption {
-          type = types.listOf types.path;
-          default = [];
-          description = ''
-            List of path(s) to respective template(s) which are copied from the 'invoice_templates/pdf' directory.
-
-            ::: {.note}
-            These templates need to be packaged before use, see example.
-            :::
-          '';
-          example = literalExpression ''
-            let
-              # Let's package an example template
-              template-vtdirektmarketing = pkgs.stdenv.mkDerivation {
-                name = "vtdirektmarketing";
-                # Download the template from a public repository
-                src = pkgs.fetchgit {
-                  url = "https://git.project-insanity.org/onny/invoiceplane-vtdirektmarketing.git";
-                  sha256 = "1hh0q7wzsh8v8x03i82p6qrgbxr4v5fb05xylyrpp975l8axyg2z";
-                };
-                sourceRoot = ".";
-                # Installing simply means copying template php file to the output directory
-                installPhase = ""
-                  mkdir -p $out
-                  cp invoiceplane-vtdirektmarketing/vtdirektmarketing.php $out/
-                "";
-              };
-            # And then pass this package to the template list like this:
-            in [ template-vtdirektmarketing ]
-          '';
-        };
-
-        poolConfig = mkOption {
-          type = with types; attrsOf (oneOf [ str int bool ]);
-          default = {
-            "pm" = "dynamic";
-            "pm.max_children" = 32;
-            "pm.start_servers" = 2;
-            "pm.min_spare_servers" = 2;
-            "pm.max_spare_servers" = 4;
-            "pm.max_requests" = 500;
-          };
-          description = ''
-            Options for the InvoicePlane PHP pool. See the documentation on `php-fpm.conf`
-            for details on configuration directives.
-          '';
-        };
-
-        settings = mkOption {
-          type = types.attrsOf types.anything;
-          default = {};
-          description = ''
-            Structural InvoicePlane configuration. Refer to
-            <https://github.com/InvoicePlane/InvoicePlane/blob/master/ipconfig.php.example>
-            for details and supported values.
-          '';
-          example = literalExpression ''
-            {
-              SETUP_COMPLETED = true;
-              DISABLE_SETUP = true;
-              IP_URL = "https://invoice.example.com";
-            }
-          '';
-        };
-
-        cron = {
-          enable = mkOption {
-            type = types.bool;
-            default = false;
-            description = ''
-              Enable cron service which periodically runs Invoiceplane tasks.
-              Requires key taken from the administration page. Refer to
-              <https://wiki.invoiceplane.com/en/1.0/modules/recurring-invoices>
-              on how to configure it.
-            '';
-          };
-          key = mkOption {
-            type = types.str;
-            description = "Cron key taken from the administration page.";
-          };
-        };
-
-      };
-
-    };
-in
-{
-  # interface
-  options = {
-    services.invoiceplane = mkOption {
-      type = types.submodule {
-
-        options.sites = mkOption {
-          type = types.attrsOf (types.submodule siteOpts);
-          default = {};
-          description = "Specification of one or more WordPress sites to serve";
-        };
-
-        options.webserver = mkOption {
-          type = types.enum [ "caddy" "nginx" ];
-          default = "caddy";
-          example = "nginx";
-          description = ''
-            Which webserver to use for virtual host management.
-          '';
-        };
-      };
-      default = {};
-      description = "InvoicePlane configuration.";
-    };
-
-  };
-
-  # implementation
-  config = mkIf (eachSite != {}) (mkMerge [{
-
-    assertions = flatten (mapAttrsToList (hostName: cfg: [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
-        message = ''services.invoiceplane.sites."${hostName}".database.user must be ${user} if the database is to be automatically provisioned'';
-      }
-      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
-        message = ''services.invoiceplane.sites."${hostName}".database.passwordFile cannot be specified if services.invoiceplane.sites."${hostName}".database.createLocally is set to true.'';
-      }
-      { assertion = cfg.cron.enable -> cfg.cron.key != null;
-        message = ''services.invoiceplane.sites."${hostName}".cron.key must be set in order to use cron service.'';
-      }
-    ]) eachSite);
-
-    services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = mapAttrsToList (hostName: cfg: cfg.database.name) eachSite;
-      ensureUsers = mapAttrsToList (hostName: cfg:
-        { name = cfg.database.user;
-          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
-        }
-      ) eachSite;
-    };
-
-    services.phpfpm = {
-      phpPackage = pkgs.php81;
-      pools = mapAttrs' (hostName: cfg: (
-        nameValuePair "invoiceplane-${hostName}" {
-          inherit user;
-          group = webserver.group;
-          settings = {
-            "listen.owner" = webserver.user;
-            "listen.group" = webserver.group;
-          } // cfg.poolConfig;
-        }
-      )) eachSite;
-    };
-
-  }
-
-  {
-
-    systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
-      "d ${cfg.stateDir} 0750 ${user} ${webserver.group} - -"
-      "f ${cfg.stateDir}/ipconfig.php 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/logs 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/uploads 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/uploads/archive 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/uploads/customer_files 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/uploads/temp 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/uploads/temp/mpdf 0750 ${user} ${webserver.group} - -"
-      "d ${cfg.stateDir}/tmp 0750 ${user} ${webserver.group} - -"
-    ]) eachSite);
-
-    systemd.services.invoiceplane-config = {
-      serviceConfig.Type = "oneshot";
-      script = concatStrings (mapAttrsToList (hostName: cfg:
-        ''
-          mkdir -p ${cfg.stateDir}/logs \
-                   ${cfg.stateDir}/uploads
-          if ! grep -q IP_URL "${cfg.stateDir}/ipconfig.php"; then
-            cp "${invoiceplane-config hostName cfg}" "${cfg.stateDir}/ipconfig.php"
-          fi
-        '') eachSite);
-      wantedBy = [ "multi-user.target" ];
-    };
-
-    users.users.${user} = {
-      group = webserver.group;
-      isSystemUser = true;
-    };
-
-  }
-  {
-
-    # Cron service implementation
-
-    systemd.timers = mapAttrs' (hostName: cfg: (
-      nameValuePair "invoiceplane-cron-${hostName}" (mkIf cfg.cron.enable {
-        wantedBy = [ "timers.target" ];
-        timerConfig = {
-          OnBootSec = "5m";
-          OnUnitActiveSec = "5m";
-          Unit = "invoiceplane-cron-${hostName}.service";
-        };
-      })
-    )) eachSite;
-
-    systemd.services =
-      mapAttrs' (hostName: cfg: (
-        nameValuePair "invoiceplane-cron-${hostName}" (mkIf cfg.cron.enable {
-          serviceConfig = {
-            Type = "oneshot";
-            User = user;
-            ExecStart = "${pkgs.curl}/bin/curl --header 'Host: ${hostName}' http://localhost/invoices/cron/recur/${cfg.cron.key}";
-          };
-        })
-    )) eachSite;
-
-  }
-
-  (mkIf (cfg.webserver == "caddy") {
-    services.caddy = {
-      enable = true;
-      virtualHosts = mapAttrs' (hostName: cfg: (
-        nameValuePair "http://${hostName}" {
-          extraConfig = ''
-            root * ${pkg hostName cfg}
-            file_server
-            php_fastcgi unix/${config.services.phpfpm.pools."invoiceplane-${hostName}".socket}
-          '';
-        }
-      )) eachSite;
-    };
-  })
-
-  (mkIf (cfg.webserver == "nginx") {
-    services.nginx = {
-      enable = true;
-      virtualHosts = mapAttrs' (hostName: cfg: (
-        nameValuePair hostName {
-          root = pkg hostName cfg;
-          extraConfig = ''
-            index index.php index.html index.htm;
-
-            if (!-e $request_filename){
-              rewrite ^(.*)$ /index.php break;
-            }
-          '';
-
-          locations = {
-            "/setup".extraConfig = ''
-              rewrite ^(.*)$ http://${hostName}/ redirect;
-            '';
-
-            "~ .php$" = {
-              extraConfig = ''
-                fastcgi_split_path_info ^(.+\.php)(/.+)$;
-                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-                fastcgi_pass unix:${config.services.phpfpm.pools."invoiceplane-${hostName}".socket};
-                include ${config.services.nginx.package}/conf/fastcgi_params;
-                include ${config.services.nginx.package}/conf/fastcgi.conf;
-              '';
-            };
-          };
-        }
-      )) eachSite;
-    };
-  })
-
-  ]);
-}
diff --git a/nixos/modules/services/web-apps/isso.nix b/nixos/modules/services/web-apps/isso.nix
deleted file mode 100644
index 4e7785d1eb3e..000000000000
--- a/nixos/modules/services/web-apps/isso.nix
+++ /dev/null
@@ -1,91 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  inherit (lib) mkEnableOption mkIf mkOption types literalExpression;
-
-  cfg = config.services.isso;
-
-  settingsFormat = pkgs.formats.ini { };
-  configFile = settingsFormat.generate "isso.conf" cfg.settings;
-in {
-
-  options = {
-    services.isso = {
-      enable = mkEnableOption ''
-        isso, a commenting server similar to Disqus.
-
-        Note: The application's author suppose to run isso behind a reverse proxy.
-        The embedded solution offered by NixOS is also only suitable for small installations
-        below 20 requests per second
-      '';
-
-      settings = mkOption {
-        description = ''
-          Configuration for `isso`.
-
-          See [Isso Server Configuration](https://posativ.org/isso/docs/configuration/server/)
-          for supported values.
-        '';
-
-        type = types.submodule {
-          freeformType = settingsFormat.type;
-        };
-
-        example = literalExpression ''
-          {
-            general = {
-              host = "http://localhost";
-            };
-          }
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.isso.settings.general.dbpath = lib.mkDefault "/var/lib/isso/comments.db";
-
-    systemd.services.isso = {
-      description = "isso, a commenting server similar to Disqus";
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        User = "isso";
-        Group = "isso";
-
-        DynamicUser = true;
-
-        StateDirectory = "isso";
-
-        ExecStart = ''
-          ${pkgs.isso}/bin/isso -c ${configFile}
-        '';
-
-        Restart = "on-failure";
-        RestartSec = 1;
-
-        # Hardening
-        CapabilityBoundingSet = [ "" ];
-        DeviceAllow = [ "" ];
-        LockPersonality = true;
-        PrivateDevices = true;
-        PrivateUsers = true;
-        ProcSubset = "pid";
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
-        UMask = "0077";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/jirafeau.nix b/nixos/modules/services/web-apps/jirafeau.nix
deleted file mode 100644
index 12a228f41d3d..000000000000
--- a/nixos/modules/services/web-apps/jirafeau.nix
+++ /dev/null
@@ -1,166 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.jirafeau;
-
-  group = config.services.nginx.group;
-  user = config.services.nginx.user;
-
-  withTrailingSlash = str: if hasSuffix "/" str then str else "${str}/";
-
-  localConfig = pkgs.writeText "config.local.php" ''
-    <?php
-      $cfg['admin_password'] = '${cfg.adminPasswordSha256}';
-      $cfg['web_root'] = 'http://${withTrailingSlash cfg.hostName}';
-      $cfg['var_root'] = '${withTrailingSlash cfg.dataDir}';
-      $cfg['maximal_upload_size'] = ${builtins.toString cfg.maxUploadSizeMegabytes};
-      $cfg['installation_done'] = true;
-
-      ${cfg.extraConfig}
-  '';
-in
-{
-  options.services.jirafeau = {
-    adminPasswordSha256 = mkOption {
-      type = types.str;
-      default = "";
-      description = ''
-        SHA-256 of the desired administration password. Leave blank/unset for no password.
-      '';
-    };
-
-    dataDir = mkOption {
-      type = types.path;
-      default = "/var/lib/jirafeau/data/";
-      description = "Location of Jirafeau storage directory.";
-    };
-
-    enable = mkEnableOption "Jirafeau file upload application";
-
-    extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-      example = ''
-        $cfg['style'] = 'courgette';
-        $cfg['organisation'] = 'ACME';
-      '';
-      description =  let
-        documentationLink =
-          "https://gitlab.com/mojo42/Jirafeau/-/blob/${cfg.package.version}/lib/config.original.php";
-      in ''
-          Jirefeau configuration. Refer to <${documentationLink}> for supported
-          values.
-        '';
-    };
-
-    hostName = mkOption {
-      type = types.str;
-      default = "localhost";
-      description = "URL of instance. Must have trailing slash.";
-    };
-
-    maxUploadSizeMegabytes = mkOption {
-      type = types.int;
-      default = 0;
-      description = "Maximum upload size of accepted files.";
-    };
-
-    maxUploadTimeout = mkOption {
-      type = types.str;
-      default = "30m";
-      description = let
-        nginxCoreDocumentation = "http://nginx.org/en/docs/http/ngx_http_core_module.html";
-      in ''
-          Timeout for reading client request bodies and headers. Refer to
-          <${nginxCoreDocumentation}#client_body_timeout> and
-          <${nginxCoreDocumentation}#client_header_timeout> for accepted values.
-        '';
-    };
-
-    nginxConfig = mkOption {
-      type = types.submodule
-        (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
-      default = {};
-      example = literalExpression ''
-        {
-          serverAliases = [ "wiki.''${config.networking.domain}" ];
-        }
-      '';
-      description = "Extra configuration for the nginx virtual host of Jirafeau.";
-    };
-
-    package = mkPackageOption pkgs "jirafeau" { };
-
-    poolConfig = mkOption {
-      type = with types; attrsOf (oneOf [ str int bool ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for Jirafeau PHP pool. See documentation on `php-fpm.conf` for
-        details on configuration directives.
-      '';
-    };
-  };
-
-
-  config = mkIf cfg.enable {
-    services = {
-      nginx = {
-        enable = true;
-        virtualHosts."${cfg.hostName}" = mkMerge [
-          cfg.nginxConfig
-          {
-            extraConfig = let
-              clientMaxBodySize =
-                if cfg.maxUploadSizeMegabytes == 0 then "0" else "${cfg.maxUploadSizeMegabytes}m";
-            in
-              ''
-                index index.php;
-                client_max_body_size ${clientMaxBodySize};
-                client_body_timeout ${cfg.maxUploadTimeout};
-                client_header_timeout ${cfg.maxUploadTimeout};
-              '';
-            locations = {
-              "~ \\.php$".extraConfig = ''
-                include ${config.services.nginx.package}/conf/fastcgi_params;
-                fastcgi_split_path_info ^(.+\.php)(/.+)$;
-                fastcgi_index index.php;
-                fastcgi_pass unix:${config.services.phpfpm.pools.jirafeau.socket};
-                fastcgi_param PATH_INFO $fastcgi_path_info;
-                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-              '';
-            };
-            root = mkForce "${cfg.package}";
-          }
-        ];
-      };
-
-      phpfpm.pools.jirafeau = {
-        inherit group user;
-        phpEnv."JIRAFEAU_CONFIG" = "${localConfig}";
-        settings = {
-          "listen.mode" = "0660";
-          "listen.owner" = user;
-          "listen.group" = group;
-        } // cfg.poolConfig;
-      };
-    };
-
-    systemd.tmpfiles.rules = [
-      "d ${cfg.dataDir} 0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/files/ 0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/links/ 0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/async/ 0750 ${user} ${group} - -"
-    ];
-  };
-
-  # uses attributes of the linked package
-  meta.buildDocsInSandbox = false;
-}
diff --git a/nixos/modules/services/web-apps/jitsi-meet.md b/nixos/modules/services/web-apps/jitsi-meet.md
deleted file mode 100644
index 577f82e315be..000000000000
--- a/nixos/modules/services/web-apps/jitsi-meet.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# Jitsi Meet {#module-services-jitsi-meet}
-
-With Jitsi Meet on NixOS you can quickly configure a complete,
-private, self-hosted video conferencing solution.
-
-## Basic usage {#module-services-jitsi-basic-usage}
-
-A minimal configuration using Let's Encrypt for TLS certificates looks like this:
-```nix
-{
-  services.jitsi-meet = {
-    enable = true;
-    hostName = "jitsi.example.com";
-  };
-  services.jitsi-videobridge.openFirewall = true;
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-  security.acme.email = "me@example.com";
-  security.acme.acceptTerms = true;
-}
-```
-
-## Configuration {#module-services-jitsi-configuration}
-
-Here is the minimal configuration with additional configurations:
-```nix
-{
-  services.jitsi-meet = {
-    enable = true;
-    hostName = "jitsi.example.com";
-    config = {
-      enableWelcomePage = false;
-      prejoinPageEnabled = true;
-      defaultLang = "fi";
-    };
-    interfaceConfig = {
-      SHOW_JITSI_WATERMARK = false;
-      SHOW_WATERMARK_FOR_GUESTS = false;
-    };
-  };
-  services.jitsi-videobridge.openFirewall = true;
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-  security.acme.email = "me@example.com";
-  security.acme.acceptTerms = true;
-}
-```
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
deleted file mode 100644
index 76753b89ec9e..000000000000
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ /dev/null
@@ -1,638 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.jitsi-meet;
-
-  # The configuration files are JS of format "var <<string>> = <<JSON>>;". In order to
-  # override only some settings, we need to extract the JSON, use jq to merge it with
-  # the config provided by user, and then reconstruct the file.
-  overrideJs =
-    source: varName: userCfg: appendExtra:
-    let
-      extractor = pkgs.writeText "extractor.js" ''
-        var fs = require("fs");
-        eval(fs.readFileSync(process.argv[2], 'utf8'));
-        process.stdout.write(JSON.stringify(eval(process.argv[3])));
-      '';
-      userJson = pkgs.writeText "user.json" (builtins.toJSON userCfg);
-    in (pkgs.runCommand "${varName}.js" { } ''
-      ${pkgs.nodejs}/bin/node ${extractor} ${source} ${varName} > default.json
-      (
-        echo "var ${varName} = "
-        ${pkgs.jq}/bin/jq -s '.[0] * .[1]' default.json ${userJson}
-        echo ";"
-        echo ${escapeShellArg appendExtra}
-      ) > $out
-    '');
-
-  # Essential config - it's probably not good to have these as option default because
-  # types.attrs doesn't do merging. Let's merge explicitly, can still be overridden if
-  # user desires.
-  defaultCfg = {
-    hosts = {
-      domain = cfg.hostName;
-      muc = "conference.${cfg.hostName}";
-      focus = "focus.${cfg.hostName}";
-      jigasi = "jigasi.${cfg.hostName}";
-    };
-    bosh = "//${cfg.hostName}/http-bind";
-    websocket = "wss://${cfg.hostName}/xmpp-websocket";
-
-    fileRecordingsEnabled = true;
-    liveStreamingEnabled = true;
-    hiddenDomain = "recorder.${cfg.hostName}";
-  };
-in
-{
-  options.services.jitsi-meet = with types; {
-    enable = mkEnableOption "Jitsi Meet - Secure, Simple and Scalable Video Conferences";
-
-    hostName = mkOption {
-      type = str;
-      example = "meet.example.org";
-      description = ''
-        FQDN of the Jitsi Meet instance.
-      '';
-    };
-
-    config = mkOption {
-      type = attrs;
-      default = { };
-      example = literalExpression ''
-        {
-          enableWelcomePage = false;
-          defaultLang = "fi";
-        }
-      '';
-      description = ''
-        Client-side web application settings that override the defaults in {file}`config.js`.
-
-        See <https://github.com/jitsi/jitsi-meet/blob/master/config.js> for default
-        configuration with comments.
-      '';
-    };
-
-    extraConfig = mkOption {
-      type = lines;
-      default = "";
-      description = ''
-        Text to append to {file}`config.js` web application config file.
-
-        Can be used to insert JavaScript logic to determine user's region in cascading bridges setup.
-      '';
-    };
-
-    interfaceConfig = mkOption {
-      type = attrs;
-      default = { };
-      example = literalExpression ''
-        {
-          SHOW_JITSI_WATERMARK = false;
-          SHOW_WATERMARK_FOR_GUESTS = false;
-        }
-      '';
-      description = ''
-        Client-side web-app interface settings that override the defaults in {file}`interface_config.js`.
-
-        See <https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js> for
-        default configuration with comments.
-      '';
-    };
-
-    videobridge = {
-      enable = mkOption {
-        type = bool;
-        default = true;
-        description = ''
-          Jitsi Videobridge instance and configure it to connect to Prosody.
-
-          Additional configuration is possible with {option}`services.jitsi-videobridge`
-        '';
-      };
-
-      passwordFile = mkOption {
-        type = nullOr str;
-        default = null;
-        example = "/run/keys/videobridge";
-        description = ''
-          File containing password to the Prosody account for videobridge.
-
-          If `null`, a file with password will be generated automatically. Setting
-          this option is useful if you plan to connect additional videobridges to the XMPP server.
-        '';
-      };
-    };
-
-    jicofo.enable = mkOption {
-      type = bool;
-      default = true;
-      description = ''
-        Whether to enable JiCoFo instance and configure it to connect to Prosody.
-
-        Additional configuration is possible with {option}`services.jicofo`.
-      '';
-    };
-
-    jibri.enable = mkOption {
-      type = bool;
-      default = false;
-      description = ''
-        Whether to enable a Jibri instance and configure it to connect to Prosody.
-
-        Additional configuration is possible with {option}`services.jibri`, and
-        {option}`services.jibri.finalizeScript` is especially useful.
-      '';
-    };
-
-    jigasi.enable = mkOption {
-      type = bool;
-      default = false;
-      description = ''
-        Whether to enable jigasi instance and configure it to connect to Prosody.
-
-        Additional configuration is possible with <option>services.jigasi</option>.
-      '';
-    };
-
-    nginx.enable = mkOption {
-      type = bool;
-      default = true;
-      description = ''
-        Whether to enable nginx virtual host that will serve the javascript application and act as
-        a proxy for the XMPP server. Further nginx configuration can be done by adapting
-        {option}`services.nginx.virtualHosts.<hostName>`.
-        When this is enabled, ACME will be used to retrieve a TLS certificate by default. To disable
-        this, set the {option}`services.nginx.virtualHosts.<hostName>.enableACME` to
-        `false` and if appropriate do the same for
-        {option}`services.nginx.virtualHosts.<hostName>.forceSSL`.
-      '';
-    };
-
-    caddy.enable = mkEnableOption "Whether to enable caddy reverse proxy to expose jitsi-meet";
-
-    prosody.enable = mkOption {
-      type = bool;
-      default = true;
-      description = ''
-        Whether to configure Prosody to relay XMPP messages between Jitsi Meet components. Turn this
-        off if you want to configure it manually.
-      '';
-    };
-
-    excalidraw.enable = mkEnableOption "Excalidraw collaboration backend for Jitsi";
-    excalidraw.port = mkOption {
-      type = types.port;
-      default = 3002;
-      description = ''The port which the Excalidraw backend for Jitsi should listen to.'';
-    };
-
-    secureDomain = {
-      enable = mkEnableOption "Authenticated room creation";
-      authentication = mkOption {
-        type = types.str;
-        default = "internal_hashed";
-        description = ''The authentication type to be used by jitsi'';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.prosody = mkIf cfg.prosody.enable {
-      enable = mkDefault true;
-      xmppComplianceSuite = mkDefault false;
-      modules = {
-        admin_adhoc = mkDefault false;
-        bosh = mkDefault true;
-        ping = mkDefault true;
-        roster = mkDefault true;
-        saslauth = mkDefault true;
-        smacks = mkDefault true;
-        tls = mkDefault true;
-        websocket = mkDefault true;
-      };
-      muc = [
-        {
-          domain = "conference.${cfg.hostName}";
-          name = "Jitsi Meet MUC";
-          roomLocking = false;
-          roomDefaultPublicJids = true;
-          extraConfig = ''
-            restrict_room_creation = true
-            storage = "memory"
-            admins = { "focus@auth.${cfg.hostName}" }
-          '';
-        }
-        {
-          domain = "breakout.${cfg.hostName}";
-          name = "Jitsi Meet Breakout MUC";
-          roomLocking = false;
-          roomDefaultPublicJids = true;
-          extraConfig = ''
-            restrict_room_creation = true
-            storage = "memory"
-            admins = { "focus@auth.${cfg.hostName}" }
-          '';
-        }
-        {
-          domain = "internal.auth.${cfg.hostName}";
-          name = "Jitsi Meet Videobridge MUC";
-          roomLocking = false;
-          roomDefaultPublicJids = true;
-          extraConfig = ''
-            storage = "memory"
-            admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}", "jigasi@auth.${cfg.hostName}" }
-          '';
-          #-- muc_room_cache_size = 1000
-        }
-        {
-          domain = "lobby.${cfg.hostName}";
-          name = "Jitsi Meet Lobby MUC";
-          roomLocking = false;
-          roomDefaultPublicJids = true;
-          extraConfig = ''
-            restrict_room_creation = true
-            storage = "memory"
-          '';
-        }
-      ];
-      extraModules = [
-        "pubsub"
-        "smacks"
-        "speakerstats"
-        "external_services"
-        "conference_duration"
-        "end_conference"
-        "muc_lobby_rooms"
-        "muc_breakout_rooms"
-        "av_moderation"
-        "muc_hide_all"
-        "muc_meeting_id"
-        "muc_domain_mapper"
-        "muc_rate_limit"
-        "limits_exception"
-        "persistent_lobby"
-        "room_metadata"
-      ];
-      extraPluginPaths = [ "${pkgs.jitsi-meet-prosody}/share/prosody-plugins" ];
-      extraConfig = lib.mkMerge [
-        (mkAfter ''
-          Component "focus.${cfg.hostName}" "client_proxy"
-            target_address = "focus@auth.${cfg.hostName}"
-
-          Component "jigasi.${cfg.hostName}" "client_proxy"
-            target_address = "jigasi@auth.${cfg.hostName}"
-
-          Component "speakerstats.${cfg.hostName}" "speakerstats_component"
-            muc_component = "conference.${cfg.hostName}"
-
-          Component "conferenceduration.${cfg.hostName}" "conference_duration_component"
-            muc_component = "conference.${cfg.hostName}"
-
-          Component "endconference.${cfg.hostName}" "end_conference"
-            muc_component = "conference.${cfg.hostName}"
-
-          Component "avmoderation.${cfg.hostName}" "av_moderation_component"
-            muc_component = "conference.${cfg.hostName}"
-
-          Component "metadata.${cfg.hostName}" "room_metadata_component"
-            muc_component = "conference.${cfg.hostName}"
-            breakout_rooms_component = "breakout.${cfg.hostName}"
-        '')
-        (mkBefore ''
-          muc_mapper_domain_base = "${cfg.hostName}"
-
-          cross_domain_websocket = true;
-          consider_websocket_secure = true;
-
-          unlimited_jids = {
-            "focus@auth.${cfg.hostName}",
-            "jvb@auth.${cfg.hostName}"
-          }
-        '')
-      ];
-      virtualHosts.${cfg.hostName} = {
-        enabled = true;
-        domain = cfg.hostName;
-        extraConfig = ''
-          authentication = ${if cfg.secureDomain.enable then "\"${cfg.secureDomain.authentication}\"" else "\"jitsi-anonymous\""}
-          c2s_require_encryption = false
-          admins = { "focus@auth.${cfg.hostName}" }
-          smacks_max_unacked_stanzas = 5
-          smacks_hibernation_time = 60
-          smacks_max_hibernated_sessions = 1
-          smacks_max_old_sessions = 1
-
-          av_moderation_component = "avmoderation.${cfg.hostName}"
-          speakerstats_component = "speakerstats.${cfg.hostName}"
-          conference_duration_component = "conferenceduration.${cfg.hostName}"
-          end_conference_component = "endconference.${cfg.hostName}"
-
-          c2s_require_encryption = false
-          lobby_muc = "lobby.${cfg.hostName}"
-          breakout_rooms_muc = "breakout.${cfg.hostName}"
-          room_metadata_component = "metadata.${cfg.hostName}"
-          main_muc = "conference.${cfg.hostName}"
-        '';
-        ssl = {
-          cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
-          key = "/var/lib/jitsi-meet/jitsi-meet.key";
-        };
-      };
-      virtualHosts."auth.${cfg.hostName}" = {
-        enabled = true;
-        domain = "auth.${cfg.hostName}";
-        extraConfig = ''
-          authentication = "internal_hashed"
-        '';
-        ssl = {
-          cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
-          key = "/var/lib/jitsi-meet/jitsi-meet.key";
-        };
-      };
-      virtualHosts."recorder.${cfg.hostName}" = {
-        enabled = true;
-        domain = "recorder.${cfg.hostName}";
-        extraConfig = ''
-          authentication = "internal_plain"
-          c2s_require_encryption = false
-        '';
-      };
-      virtualHosts."guest.${cfg.hostName}" = {
-        enabled = true;
-        domain = "guest.${cfg.hostName}";
-        extraConfig = ''
-          authentication = "anonymous"
-          c2s_require_encryption = false
-        '';
-      };
-    };
-    systemd.services.prosody = mkIf cfg.prosody.enable {
-      preStart = let
-        videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret";
-      in ''
-        ${config.services.prosody.package}/bin/prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)"
-        ${config.services.prosody.package}/bin/prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})"
-        ${config.services.prosody.package}/bin/prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName}
-        ${config.services.prosody.package}/bin/prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)"
-        ${config.services.prosody.package}/bin/prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"
-      '' + optionalString cfg.jigasi.enable ''
-        ${config.services.prosody.package}/bin/prosodyctl register jigasi auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jigasi-user-secret)"
-      '';
-
-      serviceConfig = {
-        EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ];
-        SupplementaryGroups = [ "jitsi-meet" ];
-      };
-      reloadIfChanged = true;
-    };
-
-    users.groups.jitsi-meet = { };
-    systemd.tmpfiles.rules = [
-      "d '/var/lib/jitsi-meet' 0750 root jitsi-meet - -"
-    ];
-
-    systemd.services.jitsi-meet-init-secrets = {
-      wantedBy = [ "multi-user.target" ];
-      before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service") ++ (optional cfg.jigasi.enable "jigasi.service");
-      serviceConfig = {
-        Type = "oneshot";
-      };
-
-      script = let
-        secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optionals cfg.jigasi.enable [ "jigasi-user-secret" "jigasi-component-secret" ]) ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret");
-      in
-      ''
-        cd /var/lib/jitsi-meet
-        ${concatMapStringsSep "\n" (s: ''
-          if [ ! -f ${s} ]; then
-            tr -dc a-zA-Z0-9 </dev/urandom | head -c 64 > ${s}
-            chown root:jitsi-meet ${s}
-            chmod 640 ${s}
-          fi
-        '') secrets}
-
-        # for easy access in prosody
-        echo "JICOFO_COMPONENT_SECRET=$(cat jicofo-component-secret)" > secrets-env
-        echo "JIGASI_COMPONENT_SECRET=$(cat jigasi-component-secret)" >> secrets-env
-        chown root:jitsi-meet secrets-env
-        chmod 640 secrets-env
-      ''
-      + optionalString cfg.prosody.enable ''
-        # generate self-signed certificates
-        if [ ! -f /var/lib/jitsi-meet.crt ]; then
-          ${getBin pkgs.openssl}/bin/openssl req \
-            -x509 \
-            -newkey rsa:4096 \
-            -keyout /var/lib/jitsi-meet/jitsi-meet.key \
-            -out /var/lib/jitsi-meet/jitsi-meet.crt \
-            -days 36500 \
-            -nodes \
-            -subj '/CN=${cfg.hostName}/CN=auth.${cfg.hostName}'
-          chmod 640 /var/lib/jitsi-meet/jitsi-meet.{crt,key}
-          chown root:jitsi-meet /var/lib/jitsi-meet/jitsi-meet.{crt,key}
-        fi
-      '';
-    };
-
-    systemd.services.jitsi-excalidraw = mkIf cfg.excalidraw.enable {
-      description = "Excalidraw collaboration backend for Jitsi";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      environment.PORT = toString cfg.excalidraw.port;
-
-      serviceConfig = {
-        Type = "simple";
-        ExecStart = "${pkgs.jitsi-excalidraw}/bin/jitsi-excalidraw-backend";
-        Restart = "on-failure";
-        Group = "jitsi-meet";
-      };
-    };
-
-    services.nginx = mkIf cfg.nginx.enable {
-      enable = mkDefault true;
-      virtualHosts.${cfg.hostName} = {
-        enableACME = mkDefault true;
-        forceSSL = mkDefault true;
-        root = pkgs.jitsi-meet;
-        extraConfig = ''
-          ssi on;
-        '';
-        locations."@root_path".extraConfig = ''
-          rewrite ^/(.*)$ / break;
-        '';
-        locations."~ ^/([^/\\?&:'\"]+)$".tryFiles = "$uri @root_path";
-        locations."^~ /xmpp-websocket" = {
-          priority = 100;
-          proxyPass = "http://localhost:5280/xmpp-websocket";
-          proxyWebsockets = true;
-        };
-        locations."=/http-bind" = {
-          proxyPass = "http://localhost:5280/http-bind";
-          extraConfig = ''
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-            proxy_set_header Host $host;
-          '';
-        };
-        locations."=/external_api.js" = mkDefault {
-          alias = "${pkgs.jitsi-meet}/libs/external_api.min.js";
-        };
-        locations."=/_api/room-info" = {
-          proxyPass = "http://localhost:5280/room-info";
-          extraConfig = ''
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-            proxy_set_header Host $host;
-          '';
-        };
-        locations."=/config.js" = mkDefault {
-          alias = overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config) cfg.extraConfig;
-        };
-        locations."=/interface_config.js" = mkDefault {
-          alias = overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig "";
-        };
-        locations."/socket.io/" = mkIf cfg.excalidraw.enable {
-          proxyPass = "http://127.0.0.1:${toString cfg.excalidraw.port}";
-          proxyWebsockets = true;
-        };
-      };
-    };
-
-    services.caddy = mkIf cfg.caddy.enable {
-      enable = mkDefault true;
-      virtualHosts.${cfg.hostName} = {
-        extraConfig =
-        let
-          templatedJitsiMeet = pkgs.runCommand "templated-jitsi-meet" { } ''
-            cp -R --no-preserve=all ${pkgs.jitsi-meet}/* .
-            for file in *.html **/*.html ; do
-              ${pkgs.sd}/bin/sd '<!--#include virtual="(.*)" -->' '{{ include "$1" }}' $file
-            done
-            rm config.js
-            rm interface_config.js
-            cp -R . $out
-            cp ${overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config) cfg.extraConfig} $out/config.js
-            cp ${overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig ""} $out/interface_config.js
-            cp ./libs/external_api.min.js $out/external_api.js
-          '';
-        in ''
-          handle /http-bind {
-            header Host ${cfg.hostName}
-            reverse_proxy 127.0.0.1:5280
-          }
-          handle /xmpp-websocket {
-            reverse_proxy 127.0.0.1:5280
-          }
-          handle {
-            templates
-            root * ${templatedJitsiMeet}
-            try_files {path} {path}
-            try_files {path} /index.html
-            file_server
-          }
-        '';
-      };
-    };
-
-    services.jitsi-meet.config = recursiveUpdate
-      (mkIf cfg.excalidraw.enable {
-        whiteboard = {
-          enabled = true;
-          collabServerBaseUrl = "https://${cfg.hostName}";
-        };
-      })
-      (mkIf cfg.secureDomain.enable {
-        hosts.anonymousdomain = "guest.${cfg.hostName}";
-      });
-
-    services.jitsi-videobridge = mkIf cfg.videobridge.enable {
-      enable = true;
-      xmppConfigs."localhost" = {
-        userName = "jvb";
-        domain = "auth.${cfg.hostName}";
-        passwordFile = "/var/lib/jitsi-meet/videobridge-secret";
-        mucJids = "jvbbrewery@internal.auth.${cfg.hostName}";
-        disableCertificateVerification = true;
-      };
-    };
-
-    services.jicofo = mkIf cfg.jicofo.enable {
-      enable = true;
-      xmppHost = "localhost";
-      xmppDomain = cfg.hostName;
-      userDomain = "auth.${cfg.hostName}";
-      userName = "focus";
-      userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret";
-      componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret";
-      bridgeMuc = "jvbbrewery@internal.auth.${cfg.hostName}";
-      config = mkMerge [{
-        jicofo.xmpp.service.disable-certificate-verification = true;
-        jicofo.xmpp.client.disable-certificate-verification = true;
-      }
-        (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
-          jicofo.jibri = {
-            brewery-jid = "JibriBrewery@internal.auth.${cfg.hostName}";
-            pending-timeout = "90";
-          };
-        })
-        (lib.mkIf cfg.secureDomain.enable {
-          jicofo = {
-            authentication = {
-              enabled = "true";
-              type = "XMPP";
-              login-url = cfg.hostName;
-            };
-            xmpp.client.client-proxy = "focus.${cfg.hostName}";
-          };
-        })];
-    };
-
-    services.jibri = mkIf cfg.jibri.enable {
-      enable = true;
-
-      xmppEnvironments."jitsi-meet" = {
-        xmppServerHosts = [ "localhost" ];
-        xmppDomain = cfg.hostName;
-
-        control.muc = {
-          domain = "internal.auth.${cfg.hostName}";
-          roomName = "JibriBrewery";
-          nickname = "jibri";
-        };
-
-        control.login = {
-          domain = "auth.${cfg.hostName}";
-          username = "jibri";
-          passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret";
-        };
-
-        call.login = {
-          domain = "recorder.${cfg.hostName}";
-          username = "recorder";
-          passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret";
-        };
-
-        usageTimeout = "0";
-        disableCertificateVerification = true;
-        stripFromRoomDomain = "conference.";
-      };
-    };
-
-    services.jigasi = mkIf cfg.jigasi.enable {
-      enable = true;
-      xmppHost = "localhost";
-      xmppDomain = cfg.hostName;
-      userDomain = "auth.${cfg.hostName}";
-      userName = "jigasi";
-      userPasswordFile = "/var/lib/jitsi-meet/jigasi-user-secret";
-      componentPasswordFile = "/var/lib/jitsi-meet/jigasi-component-secret";
-      bridgeMuc = "jigasibrewery@internal.${cfg.hostName}";
-      config = {
-        "org.jitsi.jigasi.ALWAYS_TRUST_MODE_ENABLED" = "true";
-      };
-    };
-  };
-
-  meta.doc = ./jitsi-meet.md;
-  meta.maintainers = lib.teams.jitsi.members;
-}
diff --git a/nixos/modules/services/web-apps/kasmweb/default.nix b/nixos/modules/services/web-apps/kasmweb/default.nix
deleted file mode 100644
index 9f1da78f3c76..000000000000
--- a/nixos/modules/services/web-apps/kasmweb/default.nix
+++ /dev/null
@@ -1,275 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.kasmweb;
-in
-{
-  options.services.kasmweb = {
-    enable = lib.mkEnableOption "kasmweb";
-
-    networkSubnet = lib.mkOption {
-      default = "172.20.0.0/16";
-      type = lib.types.str;
-      description = ''
-        The network subnet to use for the containers.
-      '';
-    };
-
-    postgres = {
-      user = lib.mkOption {
-        default = "kasmweb";
-        type = lib.types.str;
-        description = ''
-          Username to use for the postgres database.
-        '';
-      };
-      password = lib.mkOption {
-        default = "kasmweb";
-        type = lib.types.str;
-        description = ''
-          password to use for the postgres database.
-        '';
-      };
-    };
-
-    redisPassword = lib.mkOption {
-      default = "kasmweb";
-      type = lib.types.str;
-      description = ''
-        password to use for the redis cache.
-      '';
-    };
-
-    defaultAdminPassword = lib.mkOption {
-      default = "kasmweb";
-      type = lib.types.str;
-      description = ''
-        default admin password to use.
-      '';
-    };
-
-    defaultUserPassword = lib.mkOption {
-      default = "kasmweb";
-      type = lib.types.str;
-      description = ''
-        default user password to use.
-      '';
-    };
-
-    defaultManagerToken = lib.mkOption {
-      default = "kasmweb";
-      type = lib.types.str;
-      description = ''
-        default manager token to use.
-      '';
-    };
-
-    defaultGuacToken = lib.mkOption {
-      default = "kasmweb";
-      type = lib.types.str;
-      description = ''
-        default guac token to use.
-      '';
-    };
-
-    defaultRegistrationToken = lib.mkOption {
-      default = "kasmweb";
-      type = lib.types.str;
-      description = ''
-        default registration token to use.
-      '';
-    };
-
-    datastorePath = lib.mkOption {
-      type = lib.types.str;
-      default = "/var/lib/kasmweb";
-      description = ''
-        The directory used to store all data for kasmweb.
-      '';
-    };
-
-    listenAddress = lib.mkOption {
-      type = lib.types.str;
-      default = "0.0.0.0";
-      description = ''
-        The address on which kasmweb should listen.
-      '';
-    };
-
-    listenPort = lib.mkOption {
-      type = lib.types.int;
-      default = 443;
-      description = ''
-        The port on which kasmweb should listen.
-      '';
-    };
-
-    sslCertificate = lib.mkOption {
-      type = lib.types.nullOr lib.types.path;
-      default = null;
-      description = ''
-        The SSL certificate to be used for kasmweb.
-      '';
-    };
-
-    sslCertificateKey = lib.mkOption {
-      type = lib.types.nullOr lib.types.path;
-      default = null;
-      description = ''
-        The SSL certificate's key to be used for kasmweb. Make sure to specify
-        this as a string and not a literal path, so that it is not accidentally
-        included in your nixstore.
-      '';
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-
-    systemd.services = {
-      "init-kasmweb" = {
-        wantedBy = [
-          "docker-kasm_db.service"
-        ];
-        before = [
-          "docker-kasm_db.service"
-          "docker-kasm_redis.service"
-          "docker-kasm_db_init.service"
-          "docker-kasm_api.service"
-          "docker-kasm_agent.service"
-          "docker-kasm_manager.service"
-          "docker-kasm_share.service"
-          "docker-kasm_guac.service"
-          "docker-kasm_proxy.service"
-        ];
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = pkgs.substituteAll {
-            src = ./initialize_kasmweb.sh;
-            isExecutable = true;
-            binPath = lib.makeBinPath [ pkgs.docker pkgs.openssl pkgs.gnused ];
-            runtimeShell = pkgs.runtimeShell;
-            kasmweb = pkgs.kasmweb;
-            postgresUser = cfg.postgres.user;
-            postgresPassword = cfg.postgres.password;
-            inherit (cfg)
-              datastorePath
-              sslCertificate
-              sslCertificateKey
-              redisPassword
-              defaultUserPassword
-              defaultAdminPassword
-              defaultManagerToken
-              defaultRegistrationToken
-              defaultGuacToken;
-          };
-        };
-      };
-    };
-
-    virtualisation = {
-      oci-containers.containers = {
-        kasm_db = {
-          image = "postgres:12-alpine";
-          environment = {
-            POSTGRES_PASSWORD = cfg.postgres.password;
-            POSTGRES_USER = cfg.postgres.user;
-            POSTGRES_DB = "kasm";
-          };
-          volumes = [
-            "${cfg.datastorePath}/conf/database/data.sql:/docker-entrypoint-initdb.d/data.sql"
-            "${cfg.datastorePath}/conf/database/:/tmp/"
-            "kasmweb_db:/var/lib/postgresql/data"
-          ];
-          extraOptions = [ "--network=kasm_default_network" ];
-        };
-        kasm_db_init = {
-          image = "kasmweb/api:${pkgs.kasmweb.version}";
-          user = "root:root";
-          volumes = [
-            "${cfg.datastorePath}/:/opt/kasm/current/"
-            "kasmweb_api_data:/tmp"
-          ];
-          dependsOn = [ "kasm_db" ];
-          entrypoint = "/bin/bash";
-          cmd = [ "/opt/kasm/current/init_seeds.sh" ];
-          extraOptions = [ "--network=kasm_default_network" "--userns=host" ];
-        };
-        kasm_redis = {
-          image = "redis:5-alpine";
-          entrypoint = "/bin/sh";
-          cmd = [
-            "-c"
-            "redis-server --requirepass ${cfg.redisPassword}"
-          ];
-          extraOptions = [ "--network=kasm_default_network" "--userns=host" ];
-        };
-        kasm_api = {
-          image = "kasmweb/api:${pkgs.kasmweb.version}";
-          user = "root:root";
-          volumes = [
-            "${cfg.datastorePath}/:/opt/kasm/current/"
-            "kasmweb_api_data:/tmp"
-          ];
-          dependsOn = [ "kasm_db_init" ];
-          extraOptions = [ "--network=kasm_default_network" "--userns=host"  ];
-        };
-        kasm_manager = {
-          image = "kasmweb/manager:${pkgs.kasmweb.version}";
-          user = "root:root";
-          volumes = [
-            "${cfg.datastorePath}/:/opt/kasm/current/"
-          ];
-          dependsOn = [ "kasm_db" "kasm_api" ];
-          extraOptions = [ "--network=kasm_default_network" "--userns=host" "--read-only"];
-        };
-        kasm_agent = {
-          image = "kasmweb/agent:${pkgs.kasmweb.version}";
-          user = "root:root";
-          volumes = [
-            "${cfg.datastorePath}/:/opt/kasm/current/"
-            "/var/run/docker.sock:/var/run/docker.sock"
-            "${pkgs.docker}/bin/docker:/usr/bin/docker"
-            "${cfg.datastorePath}/conf/nginx:/etc/nginx/conf.d"
-          ];
-          dependsOn = [ "kasm_manager" ];
-          extraOptions = [ "--network=kasm_default_network" "--userns=host" "--read-only" ];
-        };
-        kasm_share = {
-          image = "kasmweb/share:${pkgs.kasmweb.version}";
-          user = "root:root";
-          volumes = [
-            "${cfg.datastorePath}/:/opt/kasm/current/"
-          ];
-          dependsOn = [ "kasm_db" "kasm_redis" ];
-          extraOptions = [ "--network=kasm_default_network" "--userns=host" "--read-only" ];
-        };
-        kasm_guac = {
-          image = "kasmweb/kasm-guac:${pkgs.kasmweb.version}";
-          user = "root:root";
-          volumes = [
-            "${cfg.datastorePath}/:/opt/kasm/current/"
-          ];
-          dependsOn = [ "kasm_db" "kasm_redis" ];
-          extraOptions = [ "--network=kasm_default_network" "--userns=host" "--read-only" ];
-        };
-        kasm_proxy = {
-          image = "kasmweb/nginx:latest";
-          ports = [ "${cfg.listenAddress}:${toString cfg.listenPort}:443" ];
-          user = "root:root";
-          volumes = [
-            "${cfg.datastorePath}/conf/nginx:/etc/nginx/conf.d:ro"
-            "${cfg.datastorePath}/certs/kasm_nginx.key:/etc/ssl/private/kasm_nginx.key"
-            "${cfg.datastorePath}/certs/kasm_nginx.crt:/etc/ssl/certs/kasm_nginx.crt"
-            "${cfg.datastorePath}/www:/srv/www:ro"
-            "${cfg.datastorePath}/log/nginx:/var/log/external/nginx"
-            "${cfg.datastorePath}/log/logrotate:/var/log/external/logrotate"
-          ];
-          dependsOn = [ "kasm_manager" "kasm_api" "kasm_agent" "kasm_share"
-          "kasm_guac" ];
-          extraOptions = [ "--network=kasm_default_network" "--userns=host"
-          "--network-alias=proxy"];
-        };
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/kasmweb/initialize_kasmweb.sh b/nixos/modules/services/web-apps/kasmweb/initialize_kasmweb.sh
deleted file mode 100644
index dbf043b98693..000000000000
--- a/nixos/modules/services/web-apps/kasmweb/initialize_kasmweb.sh
+++ /dev/null
@@ -1,114 +0,0 @@
-#! @runtimeShell@
-export PATH=@binPath@:$PATH
-
-mkdir -p @datastorePath@/log
-chmod -R a+rw @datastorePath@
-
-ln -sf @kasmweb@/bin @datastorePath@
-rm -r @datastorePath@/conf
-cp -r @kasmweb@/conf @datastorePath@
-mkdir -p @datastorePath@/conf/nginx/containers.d
-chmod -R a+rw @datastorePath@/conf
-ln -sf @kasmweb@/www @datastorePath@
-
-
-docker network inspect kasm_default_network >/dev/null || docker network create kasm_default_network --subnet @networkSubnet@
-if docker volume inspect kasmweb_db >/dev/null; then
-    source @datastorePath@/ids.env
-    echo 'echo "skipping database init"' > @datastorePath@/init_seeds.sh
-    echo 'while true; do sleep 10 ; done' >> @datastorePath@/init_seeds.sh
-else
-    API_SERVER_ID=$(cat /proc/sys/kernel/random/uuid)
-    MANAGER_ID=$(cat /proc/sys/kernel/random/uuid)
-    SHARE_ID=$(cat /proc/sys/kernel/random/uuid)
-    SERVER_ID=$(cat /proc/sys/kernel/random/uuid)
-    echo "export API_SERVER_ID=$API_SERVER_ID" > @datastorePath@/ids.env
-    echo "export MANAGER_ID=$MANAGER_ID" >> @datastorePath@/ids.env
-    echo "export SHARE_ID=$SHARE_ID" >> @datastorePath@/ids.env
-    echo "export SERVER_ID=$SERVER_ID" >> @datastorePath@/ids.env
-
-    mkdir -p @datastorePath@/certs
-    openssl req -x509 -nodes -days 1825 -newkey rsa:2048 -keyout @datastorePath@/certs/kasm_nginx.key -out @datastorePath@/certs/kasm_nginx.crt -subj "/C=US/ST=VA/L=None/O=None/OU=DoFu/CN=$(hostname)/emailAddress=none@none.none" 2> /dev/null
-
-    docker volume create kasmweb_db
-    rm @datastorePath@/.done_initing_data
-    cat >@datastorePath@/init_seeds.sh <<EOF
-#!/bin/bash
-if [ ! -e /opt/kasm/current/.done_initing_data ]; then
-  sleep 4
-  /usr/bin/kasm_server.so --initialize-database --cfg \
-    /opt/kasm/current/conf/app/api.app.config.yaml \
-    --populate-production \
-    --seed-file \
-    /opt/kasm/current/conf/database/seed_data/default_properties.yaml \
-    2>&1 | grep -v UserWarning
-  /usr/bin/kasm_server.so --cfg \
-    /opt/kasm/current/conf/app/api.app.config.yaml \
-    --populate-production \
-    --seed-file \
-    /opt/kasm/current/conf/database/seed_data/default_agents.yaml \
-    2>&1 | grep -v UserWarning
-  /usr/bin/kasm_server.so --cfg \
-    /opt/kasm/current/conf/app/api.app.config.yaml \
-    --populate-production \
-    --seed-file \
-    /opt/kasm/current/conf/database/seed_data/default_connection_proxies.yaml \
-    2>&1 | grep -v UserWarning
-  /usr/bin/kasm_server.so --cfg \
-    /opt/kasm/current/conf/app/api.app.config.yaml \
-    --populate-production \
-    --seed-file \
-    /opt/kasm/current/conf/database/seed_data/default_images_amd64.yaml \
-    2>&1 | grep -v UserWarning
-  touch /opt/kasm/current/.done_initing_data
-  while true; do sleep 10 ; done
-else
- echo "skipping database init"
-  while true; do sleep 10 ; done
-fi
-EOF
-fi
-
-chmod +x @datastorePath@/init_seeds.sh
-chmod a+w @datastorePath@/init_seeds.sh
-
-if [ -e @sslCertificate@ ]; then
-    cp @sslCertificate@ @datastorePath@/certs/kasm_nginx.crt
-    cp @sslCertificateKey@ @datastorePath@/certs/kasm_nginx.key
-fi
-
-sed -i -e "s/username.*/username: @postgresUser@/g" \
-    -e "s/password.*/password: @postgresPassword@/g" \
-    -e "s/host.*db/host: kasm_db/g" \
-    -e "s/ssl: true/ssl: false/g" \
-    -e "s/redisPassword.*/redisPassword: @redisPassword@/g" \
-    -e "s/server_hostname.*/server_hostname: kasm_api/g" \
-    -e "s/server_id.*/server_id: $API_SERVER_ID/g" \
-    -e "s/manager_id.*/manager_id: $MANAGER_ID/g" \
-    -e "s/share_id.*/share_id: $SHARE_ID/g" \
-    @datastorePath@/conf/app/api.app.config.yaml
-
-sed -i -e "s/ token:.*/ token: \"@defaultManagerToken@\"/g" \
-    -e "s/hostnames: \['proxy.*/hostnames: \['kasm_proxy'\]/g" \
-    -e "s/server_id.*/server_id: $SERVER_ID/g" \
-    @datastorePath@/conf/app/agent.app.config.yaml
-
-
-sed -i -e "s/password: admin.*/password: \"@defaultAdminPassword@\"/g" \
-    -e "s/password: user.*/password: \"@defaultUserPassword@\"/g" \
-    -e "s/default-manager-token/@defaultManagerToken@/g" \
-    -e "s/default-registration-token/@defaultRegistrationToken@/g" \
-    -e "s/upstream_auth_address:.*/upstream_auth_address: 'proxy'/g" \
-    @datastorePath@/conf/database/seed_data/default_properties.yaml
-
-sed -i -e "s/GUACTOKEN/@defaultGuacToken@/g" \
-    -e "s/APIHOSTNAME/proxy/g" \
-    @datastorePath@/conf/app/kasmguac.app.config.yaml
-
-sed -i -e "s/GUACTOKEN/@defaultGuacToken@/g" \
-    -e "s/APIHOSTNAME/proxy/g" \
-    @datastorePath@/conf/database/seed_data/default_connection_proxies.yaml
-
-sed -i "s/00000000-0000-0000-0000-000000000000/$SERVER_ID/g" \
-    @datastorePath@/conf/database/seed_data/default_agents.yaml
-
diff --git a/nixos/modules/services/web-apps/kavita.nix b/nixos/modules/services/web-apps/kavita.nix
deleted file mode 100644
index fe22d28af5d0..000000000000
--- a/nixos/modules/services/web-apps/kavita.nix
+++ /dev/null
@@ -1,106 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.kavita;
-  settingsFormat = pkgs.formats.json { };
-  appsettings = settingsFormat.generate "appsettings.json" ({ TokenKey = "@TOKEN@"; } // cfg.settings);
-in
-{
-  imports = [
-    (lib.mkChangedOptionModule [ "services" "kavita" "ipAdresses" ] [ "services" "kavita" "settings" "IpAddresses" ] (config:
-      let value = lib.getAttrFromPath [ "services" "kavita" "ipAdresses" ] config; in
-      lib.concatStringsSep "," value
-    ))
-    (lib.mkRenamedOptionModule [ "services" "kavita" "port" ] [ "services" "kavita" "settings" "Port" ])
-  ];
-
-  options.services.kavita = {
-    enable = lib.mkEnableOption "Kavita reading server";
-
-    user = lib.mkOption {
-      type = lib.types.str;
-      default = "kavita";
-      description = "User account under which Kavita runs.";
-    };
-
-    package = lib.mkPackageOption pkgs "kavita" { };
-
-    dataDir = lib.mkOption {
-      default = "/var/lib/kavita";
-      type = lib.types.str;
-      description = "The directory where Kavita stores its state.";
-    };
-
-    tokenKeyFile = lib.mkOption {
-      type = lib.types.path;
-      description = ''
-        A file containing the TokenKey, a secret with at 512+ bits.
-        It can be generated with `head -c 64 /dev/urandom | base64 --wrap=0`.
-      '';
-    };
-
-    settings = lib.mkOption {
-      default = { };
-      description = ''
-        Kavita configuration options, as configured in {file}`appsettings.json`.
-      '';
-      type = lib.types.submodule {
-        freeformType = settingsFormat.type;
-
-        options = {
-          Port = lib.mkOption {
-            default = 5000;
-            type = lib.types.port;
-            description = "Port to bind to.";
-          };
-
-          IpAddresses = lib.mkOption {
-            default = "0.0.0.0,::";
-            type = lib.types.commas;
-            description = ''
-              IP Addresses to bind to. The default is to bind to all IPv4 and IPv6 addresses.
-            '';
-          };
-        };
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.kavita = {
-      description = "Kavita";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-      preStart = ''
-        install -m600 ${appsettings} ${lib.escapeShellArg cfg.dataDir}/config/appsettings.json
-        ${pkgs.replace-secret}/bin/replace-secret '@TOKEN@' \
-          ''${CREDENTIALS_DIRECTORY}/token \
-          '${cfg.dataDir}/config/appsettings.json'
-      '';
-      serviceConfig = {
-        WorkingDirectory = cfg.dataDir;
-        LoadCredential = [ "token:${cfg.tokenKeyFile}" ];
-        ExecStart = lib.getExe cfg.package;
-        Restart = "always";
-        User = cfg.user;
-      };
-    };
-
-    systemd.tmpfiles.rules = [
-      "d '${cfg.dataDir}'        0750 ${cfg.user} ${cfg.user} - -"
-      "d '${cfg.dataDir}/config' 0750 ${cfg.user} ${cfg.user} - -"
-    ];
-
-    users = {
-      users.${cfg.user} = {
-        description = "kavita service user";
-        isSystemUser = true;
-        group = cfg.user;
-        home = cfg.dataDir;
-      };
-      groups.${cfg.user} = { };
-    };
-  };
-
-  meta.maintainers = with lib.maintainers; [ misterio77 ];
-}
diff --git a/nixos/modules/services/web-apps/keycloak.md b/nixos/modules/services/web-apps/keycloak.md
deleted file mode 100644
index 020bee400348..000000000000
--- a/nixos/modules/services/web-apps/keycloak.md
+++ /dev/null
@@ -1,143 +0,0 @@
-# Keycloak {#module-services-keycloak}
-
-[Keycloak](https://www.keycloak.org/) is an
-open source identity and access management server with support for
-[OpenID Connect](https://openid.net/connect/),
-[OAUTH 2.0](https://oauth.net/2/) and
-[SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0).
-
-## Administration {#module-services-keycloak-admin}
-
-An administrative user with the username
-`admin` is automatically created in the
-`master` realm. Its initial password can be
-configured by setting [](#opt-services.keycloak.initialAdminPassword)
-and defaults to `changeme`. The password is
-not stored safely and should be changed immediately in the
-admin panel.
-
-Refer to the [Keycloak Server Administration Guide](
-  https://www.keycloak.org/docs/latest/server_admin/index.html
-) for information on
-how to administer your Keycloak
-instance.
-
-## Database access {#module-services-keycloak-database}
-
-Keycloak can be used with either PostgreSQL, MariaDB or
-MySQL. Which one is used can be
-configured in [](#opt-services.keycloak.database.type). The selected
-database will automatically be enabled and a database and role
-created unless [](#opt-services.keycloak.database.host) is changed
-from its default of `localhost` or
-[](#opt-services.keycloak.database.createLocally) is set to `false`.
-
-External database access can also be configured by setting
-[](#opt-services.keycloak.database.host),
-[](#opt-services.keycloak.database.name),
-[](#opt-services.keycloak.database.username),
-[](#opt-services.keycloak.database.useSSL) and
-[](#opt-services.keycloak.database.caCert) as
-appropriate. Note that you need to manually create the database
-and allow the configured database user full access to it.
-
-[](#opt-services.keycloak.database.passwordFile)
-must be set to the path to a file containing the password used
-to log in to the database. If [](#opt-services.keycloak.database.host)
-and [](#opt-services.keycloak.database.createLocally)
-are kept at their defaults, the database role
-`keycloak` with that password is provisioned
-on the local database instance.
-
-::: {.warning}
-The path should be provided as a string, not a Nix path, since Nix
-paths are copied into the world readable Nix store.
-:::
-
-## Hostname {#module-services-keycloak-hostname}
-
-The hostname is used to build the public URL used as base for
-all frontend requests and must be configured through
-[](#opt-services.keycloak.settings.hostname).
-
-::: {.note}
-If you're migrating an old Wildfly based Keycloak instance
-and want to keep compatibility with your current clients,
-you'll likely want to set [](#opt-services.keycloak.settings.http-relative-path)
-to `/auth`. See the option description
-for more details.
-:::
-
-[](#opt-services.keycloak.settings.hostname-strict-backchannel)
-determines whether Keycloak should force all requests to go
-through the frontend URL. By default,
-Keycloak allows backend requests to
-instead use its local hostname or IP address and may also
-advertise it to clients through its OpenID Connect Discovery
-endpoint.
-
-For more information on hostname configuration, see the [Hostname
-section of the Keycloak Server Installation and Configuration
-Guide](https://www.keycloak.org/server/hostname).
-
-## Setting up TLS/SSL {#module-services-keycloak-tls}
-
-By default, Keycloak won't accept
-unsecured HTTP connections originating from outside its local
-network.
-
-HTTPS support requires a TLS/SSL certificate and a private key,
-both [PEM formatted](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail).
-Their paths should be set through
-[](#opt-services.keycloak.sslCertificate) and
-[](#opt-services.keycloak.sslCertificateKey).
-
-::: {.warning}
- The paths should be provided as a strings, not a Nix paths,
-since Nix paths are copied into the world readable Nix store.
-:::
-
-## Themes {#module-services-keycloak-themes}
-
-You can package custom themes and make them visible to
-Keycloak through [](#opt-services.keycloak.themes). See the
-[Themes section of the Keycloak Server Development Guide](
-  https://www.keycloak.org/docs/latest/server_development/#_themes
-) and the description of the aforementioned NixOS option for
-more information.
-
-## Configuration file settings {#module-services-keycloak-settings}
-
-Keycloak server configuration parameters can be set in
-[](#opt-services.keycloak.settings). These correspond
-directly to options in
-{file}`conf/keycloak.conf`. Some of the most
-important parameters are documented as suboptions, the rest can
-be found in the [All
-configuration section of the Keycloak Server Installation and
-Configuration Guide](https://www.keycloak.org/server/all-config).
-
-Options containing secret data should be set to an attribute
-set containing the attribute `_secret` - a
-string pointing to a file containing the value the option
-should be set to. See the description of
-[](#opt-services.keycloak.settings) for an example.
-
-## Example configuration {#module-services-keycloak-example-config}
-
-A basic configuration with some custom settings could look like this:
-```nix
-{
-  services.keycloak = {
-    enable = true;
-    settings = {
-      hostname = "keycloak.example.com";
-      hostname-strict-backchannel = true;
-    };
-    initialAdminPassword = "e6Wcm0RrtegMEHl";  # change on first login
-    sslCertificate = "/run/keys/ssl_cert";
-    sslCertificateKey = "/run/keys/ssl_key";
-    database.passwordFile = "/run/keys/db_password";
-  };
-}
-```
diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix
deleted file mode 100644
index 6d472cf48cd0..000000000000
--- a/nixos/modules/services/web-apps/keycloak.nix
+++ /dev/null
@@ -1,684 +0,0 @@
-{ config, options, pkgs, lib, ... }:
-
-let
-  cfg = config.services.keycloak;
-  opt = options.services.keycloak;
-
-  inherit (lib)
-    types
-    mkMerge
-    mkOption
-    mkChangedOptionModule
-    mkRenamedOptionModule
-    mkRemovedOptionModule
-    mkPackageOption
-    concatStringsSep
-    mapAttrsToList
-    escapeShellArg
-    mkIf
-    optionalString
-    optionals
-    mkDefault
-    literalExpression
-    isAttrs
-    literalMD
-    maintainers
-    catAttrs
-    collect
-    hasPrefix
-    ;
-
-  inherit (builtins)
-    elem
-    typeOf
-    isInt
-    isString
-    hashString
-    isPath
-    ;
-
-  prefixUnlessEmpty = prefix: string: optionalString (string != "") "${prefix}${string}";
-in
-{
-  imports =
-    [
-      (mkRenamedOptionModule
-        [ "services" "keycloak" "bindAddress" ]
-        [ "services" "keycloak" "settings" "http-host" ])
-      (mkRenamedOptionModule
-        [ "services" "keycloak" "forceBackendUrlToFrontendUrl"]
-        [ "services" "keycloak" "settings" "hostname-strict-backchannel"])
-      (mkChangedOptionModule
-        [ "services" "keycloak" "httpPort" ]
-        [ "services" "keycloak" "settings" "http-port" ]
-        (config:
-          builtins.fromJSON config.services.keycloak.httpPort))
-      (mkChangedOptionModule
-        [ "services" "keycloak" "httpsPort" ]
-        [ "services" "keycloak" "settings" "https-port" ]
-        (config:
-          builtins.fromJSON config.services.keycloak.httpsPort))
-      (mkRemovedOptionModule
-        [ "services" "keycloak" "frontendUrl" ]
-        ''
-          Set `services.keycloak.settings.hostname' and `services.keycloak.settings.http-relative-path' instead.
-          NOTE: You likely want to set 'http-relative-path' to '/auth' to keep compatibility with your clients.
-                See its description for more information.
-        '')
-      (mkRemovedOptionModule
-        [ "services" "keycloak" "extraConfig" ]
-        "Use `services.keycloak.settings' instead.")
-    ];
-
-  options.services.keycloak =
-    let
-      inherit (types)
-        bool
-        str
-        int
-        nullOr
-        attrsOf
-        oneOf
-        path
-        enum
-        package
-        port;
-
-      assertStringPath = optionName: value:
-        if isPath value then
-          throw ''
-            services.keycloak.${optionName}:
-              ${toString value}
-              is a Nix path, but should be a string, since Nix
-              paths are copied into the world-readable Nix store.
-          ''
-        else value;
-    in
-    {
-      enable = mkOption {
-        type = bool;
-        default = false;
-        example = true;
-        description = ''
-          Whether to enable the Keycloak identity and access management
-          server.
-        '';
-      };
-
-      sslCertificate = mkOption {
-        type = nullOr path;
-        default = null;
-        example = "/run/keys/ssl_cert";
-        apply = assertStringPath "sslCertificate";
-        description = ''
-          The path to a PEM formatted certificate to use for TLS/SSL
-          connections.
-        '';
-      };
-
-      sslCertificateKey = mkOption {
-        type = nullOr path;
-        default = null;
-        example = "/run/keys/ssl_key";
-        apply = assertStringPath "sslCertificateKey";
-        description = ''
-          The path to a PEM formatted private key to use for TLS/SSL
-          connections.
-        '';
-      };
-
-      plugins = lib.mkOption {
-        type = lib.types.listOf lib.types.path;
-        default = [ ];
-        description = ''
-          Keycloak plugin jar, ear files or derivations containing
-          them. Packaged plugins are available through
-          `pkgs.keycloak.plugins`.
-        '';
-      };
-
-      database = {
-        type = mkOption {
-          type = enum [ "mysql" "mariadb" "postgresql" ];
-          default = "postgresql";
-          example = "mariadb";
-          description = ''
-            The type of database Keycloak should connect to.
-          '';
-        };
-
-        host = mkOption {
-          type = str;
-          default = "localhost";
-          description = ''
-            Hostname of the database to connect to.
-          '';
-        };
-
-        port =
-          let
-            dbPorts = {
-              postgresql = 5432;
-              mariadb = 3306;
-              mysql = 3306;
-            };
-          in
-          mkOption {
-            type = port;
-            default = dbPorts.${cfg.database.type};
-            defaultText = literalMD "default port of selected database";
-            description = ''
-              Port of the database to connect to.
-            '';
-          };
-
-        useSSL = mkOption {
-          type = bool;
-          default = cfg.database.host != "localhost";
-          defaultText = literalExpression ''config.${opt.database.host} != "localhost"'';
-          description = ''
-            Whether the database connection should be secured by SSL /
-            TLS.
-          '';
-        };
-
-        caCert = mkOption {
-          type = nullOr path;
-          default = null;
-          description = ''
-            The SSL / TLS CA certificate that verifies the identity of the
-            database server.
-
-            Required when PostgreSQL is used and SSL is turned on.
-
-            For MySQL, if left at `null`, the default
-            Java keystore is used, which should suffice if the server
-            certificate is issued by an official CA.
-          '';
-        };
-
-        createLocally = mkOption {
-          type = bool;
-          default = true;
-          description = ''
-            Whether a database should be automatically created on the
-            local host. Set this to false if you plan on provisioning a
-            local database yourself. This has no effect if
-            services.keycloak.database.host is customized.
-          '';
-        };
-
-        name = mkOption {
-          type = str;
-          default = "keycloak";
-          description = ''
-            Database name to use when connecting to an external or
-            manually provisioned database; has no effect when a local
-            database is automatically provisioned.
-
-            To use this with a local database, set [](#opt-services.keycloak.database.createLocally) to
-            `false` and create the database and user
-            manually.
-          '';
-        };
-
-        username = mkOption {
-          type = str;
-          default = "keycloak";
-          description = ''
-            Username to use when connecting to an external or manually
-            provisioned database; has no effect when a local database is
-            automatically provisioned.
-
-            To use this with a local database, set [](#opt-services.keycloak.database.createLocally) to
-            `false` and create the database and user
-            manually.
-          '';
-        };
-
-        passwordFile = mkOption {
-          type = path;
-          example = "/run/keys/db_password";
-          apply = assertStringPath "passwordFile";
-          description = ''
-            The path to a file containing the database password.
-          '';
-        };
-      };
-
-      package = mkPackageOption pkgs "keycloak" { };
-
-      initialAdminPassword = mkOption {
-        type = str;
-        default = "changeme";
-        description = ''
-          Initial password set for the `admin`
-          user. The password is not stored safely and should be changed
-          immediately in the admin panel.
-        '';
-      };
-
-      themes = mkOption {
-        type = attrsOf package;
-        default = { };
-        description = ''
-          Additional theme packages for Keycloak. Each theme is linked into
-          subdirectory with a corresponding attribute name.
-
-          Theme packages consist of several subdirectories which provide
-          different theme types: for example, `account`,
-          `login` etc. After adding a theme to this option you
-          can select it by its name in Keycloak administration console.
-        '';
-      };
-
-      settings = mkOption {
-        type = lib.types.submodule {
-          freeformType = attrsOf (nullOr (oneOf [ str int bool (attrsOf path) ]));
-
-          options = {
-            http-host = mkOption {
-              type = str;
-              default = "0.0.0.0";
-              example = "127.0.0.1";
-              description = ''
-                On which address Keycloak should accept new connections.
-              '';
-            };
-
-            http-port = mkOption {
-              type = port;
-              default = 80;
-              example = 8080;
-              description = ''
-                On which port Keycloak should listen for new HTTP connections.
-              '';
-            };
-
-            https-port = mkOption {
-              type = port;
-              default = 443;
-              example = 8443;
-              description = ''
-                On which port Keycloak should listen for new HTTPS connections.
-              '';
-            };
-
-            http-relative-path = mkOption {
-              type = str;
-              default = "/";
-              example = "/auth";
-              apply = x: if !(hasPrefix "/") x then "/" + x else x;
-              description = ''
-                The path relative to `/` for serving
-                resources.
-
-                ::: {.note}
-                In versions of Keycloak using Wildfly (&lt;17),
-                this defaulted to `/auth`. If
-                upgrading from the Wildfly version of Keycloak,
-                i.e. a NixOS version before 22.05, you'll likely
-                want to set this to `/auth` to
-                keep compatibility with your clients.
-
-                See <https://www.keycloak.org/migration/migrating-to-quarkus>
-                for more information on migrating from Wildfly to Quarkus.
-                :::
-              '';
-            };
-
-            hostname = mkOption {
-              type = nullOr str;
-              default = null;
-              example = "keycloak.example.com";
-              description = ''
-                The hostname part of the public URL used as base for
-                all frontend requests.
-
-                See <https://www.keycloak.org/server/hostname>
-                for more information about hostname configuration.
-              '';
-            };
-
-            hostname-strict-backchannel = mkOption {
-              type = bool;
-              default = false;
-              example = true;
-              description = ''
-                Whether Keycloak should force all requests to go
-                through the frontend URL. By default, Keycloak allows
-                backend requests to instead use its local hostname or
-                IP address and may also advertise it to clients
-                through its OpenID Connect Discovery endpoint.
-
-                See <https://www.keycloak.org/server/hostname>
-                for more information about hostname configuration.
-              '';
-            };
-
-            proxy = mkOption {
-              type = enum [ "edge" "reencrypt" "passthrough" "none" ];
-              default = "none";
-              example = "edge";
-              description = ''
-                The proxy address forwarding mode if the server is
-                behind a reverse proxy.
-
-                - `edge`:
-                  Enables communication through HTTP between the
-                  proxy and Keycloak.
-                - `reencrypt`:
-                  Requires communication through HTTPS between the
-                  proxy and Keycloak.
-                - `passthrough`:
-                  Enables communication through HTTP or HTTPS between
-                  the proxy and Keycloak.
-
-                See <https://www.keycloak.org/server/reverseproxy> for more information.
-              '';
-            };
-          };
-        };
-
-        example = literalExpression ''
-          {
-            hostname = "keycloak.example.com";
-            proxy = "reencrypt";
-            https-key-store-file = "/path/to/file";
-            https-key-store-password = { _secret = "/run/keys/store_password"; };
-          }
-        '';
-
-        description = ''
-          Configuration options corresponding to parameters set in
-          {file}`conf/keycloak.conf`.
-
-          Most available options are documented at <https://www.keycloak.org/server/all-config>.
-
-          Options containing secret data should be set to an attribute
-          set containing the attribute `_secret` - a
-          string pointing to a file containing the value the option
-          should be set to. See the example to get a better picture of
-          this: in the resulting
-          {file}`conf/keycloak.conf` file, the
-          `https-key-store-password` key will be set
-          to the contents of the
-          {file}`/run/keys/store_password` file.
-        '';
-      };
-    };
-
-  config =
-    let
-      # We only want to create a database if we're actually going to
-      # connect to it.
-      databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "localhost";
-      createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.database.type == "postgresql";
-      createLocalMySQL = databaseActuallyCreateLocally && elem cfg.database.type [ "mysql" "mariadb" ];
-
-      mySqlCaKeystore = pkgs.runCommand "mysql-ca-keystore" { } ''
-        ${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt
-      '';
-
-      # Both theme and theme type directories need to be actual
-      # directories in one hierarchy to pass Keycloak checks.
-      themesBundle = pkgs.runCommand "keycloak-themes" { } ''
-        linkTheme() {
-          theme="$1"
-          name="$2"
-
-          mkdir "$out/$name"
-          for typeDir in "$theme"/*; do
-            if [ -d "$typeDir" ]; then
-              type="$(basename "$typeDir")"
-              mkdir "$out/$name/$type"
-              for file in "$typeDir"/*; do
-                ln -sn "$file" "$out/$name/$type/$(basename "$file")"
-              done
-            fi
-          done
-        }
-
-        mkdir -p "$out"
-        for theme in ${keycloakBuild}/themes/*; do
-          if [ -d "$theme" ]; then
-            linkTheme "$theme" "$(basename "$theme")"
-          fi
-        done
-
-        ${concatStringsSep "\n" (mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes)}
-      '';
-
-      keycloakConfig = lib.generators.toKeyValue {
-        mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
-          mkValueString = v:
-            if isInt v then toString v
-            else if isString v then v
-            else if true == v then "true"
-            else if false == v then "false"
-            else if isSecret v then hashString "sha256" v._secret
-            else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
-        };
-      };
-
-      isSecret = v: isAttrs v && v ? _secret && isString v._secret;
-      filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{ } null])) cfg.settings;
-      confFile = pkgs.writeText "keycloak.conf" (keycloakConfig filteredConfig);
-      keycloakBuild = cfg.package.override {
-        inherit confFile;
-        plugins = cfg.package.enabledPlugins ++ cfg.plugins ++
-                  (with cfg.package.plugins; [quarkus-systemd-notify quarkus-systemd-notify-deployment]);
-      };
-    in
-    mkIf cfg.enable
-      {
-        assertions = [
-          {
-            assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null);
-            message = "A CA certificate must be specified (in 'services.keycloak.database.caCert') when PostgreSQL is used with SSL";
-          }
-          {
-            assertion = createLocalPostgreSQL -> config.services.postgresql.settings.standard_conforming_strings or true;
-            message = "Setting up a local PostgreSQL db for Keycloak requires `standard_conforming_strings` turned on to work reliably";
-          }
-          {
-            assertion = cfg.settings.hostname != null || cfg.settings.hostname-url or null != null;
-            message = "Setting the Keycloak hostname is required, see `services.keycloak.settings.hostname`";
-          }
-          {
-            assertion = !(cfg.settings.hostname != null && cfg.settings.hostname-url or null != null);
-            message = "`services.keycloak.settings.hostname` and `services.keycloak.settings.hostname-url` are mutually exclusive";
-          }
-        ];
-
-        environment.systemPackages = [ keycloakBuild ];
-
-        services.keycloak.settings =
-          let
-            postgresParams = concatStringsSep "&" (
-              optionals cfg.database.useSSL [
-                "ssl=true"
-              ] ++ optionals (cfg.database.caCert != null) [
-                "sslrootcert=${cfg.database.caCert}"
-                "sslmode=verify-ca"
-              ]
-            );
-            mariadbParams = concatStringsSep "&" ([
-              "characterEncoding=UTF-8"
-            ] ++ optionals cfg.database.useSSL [
-              "useSSL=true"
-              "requireSSL=true"
-              "verifyServerCertificate=true"
-            ] ++ optionals (cfg.database.caCert != null) [
-              "trustCertificateKeyStoreUrl=file:${mySqlCaKeystore}"
-              "trustCertificateKeyStorePassword=notsosecretpassword"
-            ]);
-            dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams;
-          in
-          mkMerge [
-            {
-              db = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type;
-              db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username;
-              db-password._secret = cfg.database.passwordFile;
-              db-url-host = cfg.database.host;
-              db-url-port = toString cfg.database.port;
-              db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name;
-              db-url-properties = prefixUnlessEmpty "?" dbProps;
-              db-url = null;
-            }
-            (mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) {
-              https-certificate-file = "/run/keycloak/ssl/ssl_cert";
-              https-certificate-key-file = "/run/keycloak/ssl/ssl_key";
-            })
-          ];
-
-        systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL {
-          after = [ "postgresql.service" ];
-          before = [ "keycloak.service" ];
-          bindsTo = [ "postgresql.service" ];
-          path = [ config.services.postgresql.package ];
-          serviceConfig = {
-            Type = "oneshot";
-            RemainAfterExit = true;
-            User = "postgres";
-            Group = "postgres";
-            LoadCredential = [ "db_password:${cfg.database.passwordFile}" ];
-          };
-          script = ''
-            set -o errexit -o pipefail -o nounset -o errtrace
-            shopt -s inherit_errexit
-
-            create_role="$(mktemp)"
-            trap 'rm -f "$create_role"' EXIT
-
-            # Read the password from the credentials directory and
-            # escape any single quotes by adding additional single
-            # quotes after them, following the rules laid out here:
-            # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS
-            db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")"
-            db_password="''${db_password//\'/\'\'}"
-
-            echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB" > "$create_role"
-            psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role"
-            psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"'
-          '';
-        };
-
-        systemd.services.keycloakMySQLInit = mkIf createLocalMySQL {
-          after = [ "mysql.service" ];
-          before = [ "keycloak.service" ];
-          bindsTo = [ "mysql.service" ];
-          path = [ config.services.mysql.package ];
-          serviceConfig = {
-            Type = "oneshot";
-            RemainAfterExit = true;
-            User = config.services.mysql.user;
-            Group = config.services.mysql.group;
-            LoadCredential = [ "db_password:${cfg.database.passwordFile}" ];
-          };
-          script = ''
-            set -o errexit -o pipefail -o nounset -o errtrace
-            shopt -s inherit_errexit
-
-            # Read the password from the credentials directory and
-            # escape any single quotes by adding additional single
-            # quotes after them, following the rules laid out here:
-            # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html
-            db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")"
-            db_password="''${db_password//\'/\'\'}"
-
-            ( echo "SET sql_mode = 'NO_BACKSLASH_ESCAPES';"
-              echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';"
-              echo "CREATE DATABASE IF NOT EXISTS keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;"
-              echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';"
-            ) | mysql -N
-          '';
-        };
-
-        systemd.services.keycloak =
-          let
-            databaseServices =
-              if createLocalPostgreSQL then [
-                "keycloakPostgreSQLInit.service"
-                "postgresql.service"
-              ]
-              else if createLocalMySQL then [
-                "keycloakMySQLInit.service"
-                "mysql.service"
-              ]
-              else [ ];
-            secretPaths = catAttrs "_secret" (collect isSecret cfg.settings);
-            mkSecretReplacement = file: ''
-              replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf
-            '';
-            secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
-          in
-          {
-            after = databaseServices;
-            bindsTo = databaseServices;
-            wantedBy = [ "multi-user.target" ];
-            path = with pkgs; [
-              keycloakBuild
-              openssl
-              replace-secret
-            ];
-            environment = {
-              KC_HOME_DIR = "/run/keycloak";
-              KC_CONF_DIR = "/run/keycloak/conf";
-            };
-            serviceConfig = {
-              LoadCredential =
-                map (p: "${baseNameOf p}:${p}") secretPaths
-                ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [
-                  "ssl_cert:${cfg.sslCertificate}"
-                  "ssl_key:${cfg.sslCertificateKey}"
-                ];
-              User = "keycloak";
-              Group = "keycloak";
-              DynamicUser = true;
-              RuntimeDirectory = "keycloak";
-              RuntimeDirectoryMode = "0700";
-              AmbientCapabilities = "CAP_NET_BIND_SERVICE";
-              Type = "notify";  # Requires quarkus-systemd-notify plugin
-              NotifyAccess = "all";
-            };
-            script = ''
-              set -o errexit -o pipefail -o nounset -o errtrace
-              shopt -s inherit_errexit
-
-              umask u=rwx,g=,o=
-
-              ln -s ${themesBundle} /run/keycloak/themes
-              ln -s ${keycloakBuild}/providers /run/keycloak/
-
-              install -D -m 0600 ${confFile} /run/keycloak/conf/keycloak.conf
-
-              ${secretReplacements}
-
-              # Escape any backslashes in the db parameters, since
-              # they're otherwise unexpectedly read as escape
-              # sequences.
-              sed -i '/db-/ s|\\|\\\\|g' /run/keycloak/conf/keycloak.conf
-
-            '' + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) ''
-              mkdir -p /run/keycloak/ssl
-              cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/
-            '' + ''
-              export KEYCLOAK_ADMIN=admin
-              export KEYCLOAK_ADMIN_PASSWORD=${escapeShellArg cfg.initialAdminPassword}
-              kc.sh --verbose start --optimized
-            '';
-          };
-
-        services.postgresql.enable = mkDefault createLocalPostgreSQL;
-        services.mysql.enable = mkDefault createLocalMySQL;
-        services.mysql.package =
-          let
-            dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80;
-          in
-          mkIf createLocalMySQL (mkDefault dbPkg);
-      };
-
-  meta.doc = ./keycloak.md;
-  meta.maintainers = [ maintainers.talyz ];
-}
diff --git a/nixos/modules/services/web-apps/komga.nix b/nixos/modules/services/web-apps/komga.nix
deleted file mode 100644
index d7ab2a9e612e..000000000000
--- a/nixos/modules/services/web-apps/komga.nix
+++ /dev/null
@@ -1,122 +0,0 @@
-{
-  config,
-  pkgs,
-  lib,
-  ...
-}:
-
-let
-  cfg = config.services.komga;
-  inherit (lib) mkOption mkEnableOption maintainers;
-  inherit (lib.types) port str bool;
-in
-{
-  options = {
-    services.komga = {
-      enable = mkEnableOption "Komga, a free and open source comics/mangas media server";
-
-      port = mkOption {
-        type = port;
-        default = 8080;
-        description = "The port that Komga will listen on.";
-      };
-
-      user = mkOption {
-        type = str;
-        default = "komga";
-        description = "User account under which Komga runs.";
-      };
-
-      group = mkOption {
-        type = str;
-        default = "komga";
-        description = "Group under which Komga runs.";
-      };
-
-      stateDir = mkOption {
-        type = str;
-        default = "/var/lib/komga";
-        description = "State and configuration directory Komga will use.";
-      };
-
-      openFirewall = mkOption {
-        type = bool;
-        default = false;
-        description = "Whether to open the firewall for the port in {option}`services.komga.port`.";
-      };
-    };
-  };
-
-  config =
-    let
-      inherit (lib) mkIf getExe;
-    in
-    mkIf cfg.enable {
-
-      networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
-
-      users.groups = mkIf (cfg.group == "komga") { komga = { }; };
-
-      users.users = mkIf (cfg.user == "komga") {
-        komga = {
-          group = cfg.group;
-          home = cfg.stateDir;
-          description = "Komga Daemon user";
-          isSystemUser = true;
-        };
-      };
-
-      systemd.services.komga = {
-        environment = {
-          SERVER_PORT = builtins.toString cfg.port;
-          KOMGA_CONFIGDIR = cfg.stateDir;
-        };
-
-        description = "Komga is a free and open source comics/mangas media server";
-
-        wantedBy = [ "multi-user.target" ];
-        wants = [ "network-online.target" ];
-        after = [ "network-online.target" ];
-
-        serviceConfig = {
-          User = cfg.user;
-          Group = cfg.group;
-
-          Type = "simple";
-          Restart = "on-failure";
-          ExecStart = getExe pkgs.komga;
-
-          StateDirectory = mkIf (cfg.stateDir == "/var/lib/komga") "komga";
-
-          RemoveIPC = true;
-          NoNewPrivileges = true;
-          CapabilityBoundingSet = "";
-          SystemCallFilter = [ "@system-service" ];
-          ProtectSystem = "full";
-          PrivateTmp = true;
-          ProtectProc = "invisible";
-          ProtectClock = true;
-          ProcSubset = "pid";
-          PrivateUsers = true;
-          PrivateDevices = true;
-          ProtectHostname = true;
-          ProtectKernelTunables = true;
-          RestrictAddressFamilies = [
-            "AF_INET"
-            "AF_INET6"
-            "AF_NETLINK"
-          ];
-          LockPersonality = true;
-          RestrictNamespaces = true;
-          ProtectKernelLogs = true;
-          ProtectControlGroups = true;
-          ProtectKernelModules = true;
-          SystemCallArchitectures = "native";
-          RestrictSUIDSGID = true;
-          RestrictRealtime = true;
-        };
-      };
-    };
-
-  meta.maintainers = with maintainers; [ govanify ];
-}
diff --git a/nixos/modules/services/web-apps/lanraragi.nix b/nixos/modules/services/web-apps/lanraragi.nix
deleted file mode 100644
index 7b7fb01918bb..000000000000
--- a/nixos/modules/services/web-apps/lanraragi.nix
+++ /dev/null
@@ -1,93 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-let
-  cfg = config.services.lanraragi;
-in
-{
-  meta.maintainers = with lib.maintainers; [ tomasajt ];
-
-  options.services = {
-    lanraragi = {
-      enable = lib.mkEnableOption "LANraragi";
-      package = lib.mkPackageOption pkgs "lanraragi" { };
-
-      port = lib.mkOption {
-        type = lib.types.port;
-        default = 3000;
-        description = "Port for LANraragi's web interface.";
-      };
-
-      passwordFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/run/keys/lanraragi-password";
-        description = ''
-          A file containing the password for LANraragi's admin interface.
-        '';
-      };
-
-      redis = {
-        port = lib.mkOption {
-          type = lib.types.port;
-          default = 6379;
-          description = "Port for LANraragi's Redis server.";
-        };
-        passwordFile = lib.mkOption {
-          type = lib.types.nullOr lib.types.path;
-          default = null;
-          example = "/run/keys/redis-lanraragi-password";
-          description = ''
-            A file containing the password for LANraragi's Redis server.
-          '';
-        };
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    services.redis.servers.lanraragi = {
-      enable = true;
-      port = cfg.redis.port;
-      requirePassFile = cfg.redis.passwordFile;
-    };
-
-    systemd.services.lanraragi = {
-      description = "LANraragi main service";
-      after = [ "network.target" "redis-lanraragi.service" ];
-      requires = [ "redis-lanraragi.service" ];
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        ExecStart = lib.getExe cfg.package;
-        DynamicUser = true;
-        StateDirectory = "lanraragi";
-        RuntimeDirectory = "lanraragi";
-        LogsDirectory = "lanraragi";
-        Restart = "on-failure";
-        WorkingDirectory = "/var/lib/lanraragi";
-      };
-      environment = {
-        "LRR_TEMP_DIRECTORY" = "/run/lanraragi";
-        "LRR_LOG_DIRECTORY" = "/var/log/lanraragi";
-        "LRR_NETWORK" = "http://*:${toString cfg.port}";
-        "HOME" = "/var/lib/lanraragi";
-      };
-      preStart = ''
-        cat > lrr.conf <<EOF
-        {
-          redis_address => "127.0.0.1:${toString cfg.redis.port}",
-          redis_password => "${lib.optionalString (cfg.redis.passwordFile != null) ''$(head -n1 ${cfg.redis.passwordFile})''}",
-          redis_database => "0",
-          redis_database_minion => "1",
-          redis_database_config => "2",
-          redis_database_search => "3",
-        }
-        EOF
-      '' + lib.optionalString (cfg.passwordFile != null) ''
-        ${lib.getExe pkgs.redis} -h 127.0.0.1 -p ${toString cfg.redis.port} ${lib.optionalString (cfg.redis.passwordFile != null) ''-a "$(head -n1 ${cfg.redis.passwordFile})"''}<<EOF
-          SELECT 2
-          HSET LRR_CONFIG password $(${cfg.package}/bin/helpers/lrr-make-password-hash $(head -n1 ${cfg.passwordFile}))
-        EOF
-      '';
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/lemmy.md b/nixos/modules/services/web-apps/lemmy.md
deleted file mode 100644
index 0ed23607d00b..000000000000
--- a/nixos/modules/services/web-apps/lemmy.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# Lemmy {#module-services-lemmy}
-
-Lemmy is a federated alternative to reddit in rust.
-
-## Quickstart {#module-services-lemmy-quickstart}
-
-the minimum to start lemmy is
-
-```nix
-{
-  services.lemmy = {
-    enable = true;
-    settings = {
-      hostname = "lemmy.union.rocks";
-      database.createLocally = true;
-    };
-    caddy.enable = true;
-  };
-}
-```
-
-this will start the backend on port 8536 and the frontend on port 1234.
-It will expose your instance with a caddy reverse proxy to the hostname you've provided.
-Postgres will be initialized on that same instance automatically.
-
-## Usage {#module-services-lemmy-usage}
-
-On first connection you will be asked to define an admin user.
-
-## Missing {#module-services-lemmy-missing}
-
-- Exposing with nginx is not implemented yet.
-- This has been tested using a local database with a unix socket connection. Using different database settings will likely require modifications
diff --git a/nixos/modules/services/web-apps/lemmy.nix b/nixos/modules/services/web-apps/lemmy.nix
deleted file mode 100644
index 3185f9a4263c..000000000000
--- a/nixos/modules/services/web-apps/lemmy.nix
+++ /dev/null
@@ -1,315 +0,0 @@
-{ lib, pkgs, config, utils, ... }:
-with lib;
-let
-  cfg = config.services.lemmy;
-  settingsFormat = pkgs.formats.json { };
-in
-{
-  meta.maintainers = with maintainers; [ happysalada ];
-  meta.doc = ./lemmy.md;
-
-  imports = [
-    (mkRemovedOptionModule [ "services" "lemmy" "jwtSecretPath" ] "As of v0.13.0, Lemmy auto-generates the JWT secret.")
-  ];
-
-  options.services.lemmy = {
-
-    enable = mkEnableOption "lemmy a federated alternative to reddit in rust";
-
-    server = {
-      package = mkPackageOption pkgs "lemmy-server" {};
-    };
-
-    ui = {
-      package = mkPackageOption pkgs "lemmy-ui" {};
-
-      port = mkOption {
-        type = types.port;
-        default = 1234;
-        description = "Port where lemmy-ui should listen for incoming requests.";
-      };
-    };
-
-    caddy.enable = mkEnableOption "exposing lemmy with the caddy reverse proxy";
-    nginx.enable = mkEnableOption "exposing lemmy with the nginx reverse proxy";
-
-    database = {
-      createLocally = mkEnableOption "creation of database on the instance";
-
-      uri = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = "The connection URI to use. Takes priority over the configuration file if set.";
-      };
-
-      uriFile = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        description = "File which contains the database uri.";
-      };
-    };
-
-    pictrsApiKeyFile = mkOption {
-      type = with types; nullOr path;
-      default = null;
-      description = "File which contains the value of `pictrs.api_key`.";
-    };
-
-    smtpPasswordFile = mkOption {
-      type = with types; nullOr path;
-      default = null;
-      description = "File which contains the value of `email.smtp_password`.";
-    };
-
-    adminPasswordFile = mkOption {
-      type = with types; nullOr path;
-      default = null;
-      description = "File which contains the value of `setup.admin_password`.";
-    };
-
-    settings = mkOption {
-      default = { };
-      description = "Lemmy configuration";
-
-      type = types.submodule {
-        freeformType = settingsFormat.type;
-
-        options.hostname = mkOption {
-          type = types.str;
-          default = null;
-          description = "The domain name of your instance (eg 'lemmy.ml').";
-        };
-
-        options.port = mkOption {
-          type = types.port;
-          default = 8536;
-          description = "Port where lemmy should listen for incoming requests.";
-        };
-
-        options.captcha = {
-          enabled = mkOption {
-            type = types.bool;
-            default = true;
-            description = "Enable Captcha.";
-          };
-          difficulty = mkOption {
-            type = types.enum [ "easy" "medium" "hard" ];
-            default = "medium";
-            description = "The difficultly of the captcha to solve.";
-          };
-        };
-      };
-    };
-  };
-
-  config =
-    let
-      secretOptions = {
-        pictrsApiKeyFile = { setting = [ "pictrs" "api_key" ]; path = cfg.pictrsApiKeyFile; };
-        smtpPasswordFile = { setting = [ "email" "smtp_password" ]; path = cfg.smtpPasswordFile; };
-        adminPasswordFile = { setting = [ "setup" "admin_password" ]; path = cfg.adminPasswordFile; };
-        uriFile = { setting = [ "database" "uri" ]; path = cfg.database.uriFile; };
-      };
-      secrets = lib.filterAttrs (option: data: data.path != null) secretOptions;
-    in
-    lib.mkIf cfg.enable {
-      services.lemmy.settings = lib.attrsets.recursiveUpdate (mapAttrs (name: mkDefault)
-        {
-          bind = "127.0.0.1";
-          tls_enabled = true;
-          pictrs = {
-            url = with config.services.pict-rs; "http://${address}:${toString port}";
-          };
-          actor_name_max_length = 20;
-
-          rate_limit.message = 180;
-          rate_limit.message_per_second = 60;
-          rate_limit.post = 6;
-          rate_limit.post_per_second = 600;
-          rate_limit.register = 3;
-          rate_limit.register_per_second = 3600;
-          rate_limit.image = 6;
-          rate_limit.image_per_second = 3600;
-        } // {
-          database = mapAttrs (name: mkDefault) {
-            user = "lemmy";
-            host = "/run/postgresql";
-            port = 5432;
-            database = "lemmy";
-            pool_size = 5;
-          };
-        }) (lib.foldlAttrs (acc: option: data: acc // lib.setAttrByPath data.setting { _secret = option; }) {} secrets);
-        # the option name is the id of the credential loaded by LoadCredential
-
-      services.postgresql = mkIf cfg.database.createLocally {
-        enable = true;
-        ensureDatabases = [ cfg.settings.database.database ];
-        ensureUsers = [{
-          name = cfg.settings.database.user;
-          ensureDBOwnership = true;
-        }];
-      };
-
-      services.pict-rs.enable = true;
-
-      services.caddy = mkIf cfg.caddy.enable {
-        enable = mkDefault true;
-        virtualHosts."${cfg.settings.hostname}" = {
-          extraConfig = ''
-            handle_path /static/* {
-              root * ${cfg.ui.package}/dist
-              file_server
-            }
-            handle_path /static/${cfg.ui.package.passthru.commit_sha}/* {
-              root * ${cfg.ui.package}/dist
-              file_server
-            }
-            @for_backend {
-              path /api/* /pictrs/* /feeds/* /nodeinfo/*
-            }
-            handle @for_backend {
-              reverse_proxy 127.0.0.1:${toString cfg.settings.port}
-            }
-            @post {
-              method POST
-            }
-            handle @post {
-              reverse_proxy 127.0.0.1:${toString cfg.settings.port}
-            }
-            @jsonld {
-              header Accept "application/activity+json"
-              header Accept "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
-            }
-            handle @jsonld {
-              reverse_proxy 127.0.0.1:${toString cfg.settings.port}
-            }
-            handle {
-              reverse_proxy 127.0.0.1:${toString cfg.ui.port}
-            }
-          '';
-        };
-      };
-
-      services.nginx = mkIf cfg.nginx.enable {
-        enable = mkDefault true;
-        virtualHosts."${cfg.settings.hostname}".locations = let
-          ui = "http://127.0.0.1:${toString cfg.ui.port}";
-          backend = "http://127.0.0.1:${toString cfg.settings.port}";
-        in {
-          "~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = {
-            # backend requests
-            proxyPass = backend;
-            proxyWebsockets = true;
-            recommendedProxySettings = true;
-          };
-          "/" = {
-            # mixed frontend and backend requests, based on the request headers
-            recommendedProxySettings = true;
-            extraConfig = ''
-              set $proxpass "${ui}";
-              if ($http_accept = "application/activity+json") {
-                set $proxpass "${backend}";
-              }
-              if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
-                set $proxpass "${backend}";
-              }
-              if ($request_method = POST) {
-                set $proxpass "${backend}";
-              }
-
-              # Cuts off the trailing slash on URLs to make them valid
-              rewrite ^(.+)/+$ $1 permanent;
-
-              proxy_pass $proxpass;
-            '';
-          };
-        };
-      };
-
-      assertions = [
-        {
-          assertion = cfg.database.createLocally -> cfg.settings.database.host == "localhost" || cfg.settings.database.host == "/run/postgresql";
-          message = "if you want to create the database locally, you need to use a local database";
-        }
-        {
-          assertion = (!(hasAttrByPath ["federation"] cfg.settings)) && (!(hasAttrByPath ["federation" "enabled"] cfg.settings));
-          message = "`services.lemmy.settings.federation` was removed in 0.17.0 and no longer has any effect";
-        }
-        {
-          assertion = cfg.database.uriFile != null -> cfg.database.uri == null && !cfg.database.createLocally;
-          message = "specifying a database uri while also specifying a database uri file is not allowed";
-        }
-      ];
-
-      systemd.services.lemmy = let
-        substitutedConfig = "/run/lemmy/config.hjson";
-      in {
-        description = "Lemmy server";
-
-        environment = {
-          LEMMY_CONFIG_LOCATION = if secrets == {} then settingsFormat.generate "config.hjson" cfg.settings else substitutedConfig;
-          LEMMY_DATABASE_URL = if cfg.database.uri != null then cfg.database.uri else (mkIf (cfg.database.createLocally) "postgres:///lemmy?host=/run/postgresql&user=lemmy");
-        };
-
-        documentation = [
-          "https://join-lemmy.org/docs/en/admins/from_scratch.html"
-          "https://join-lemmy.org/docs/en/"
-        ];
-
-        wantedBy = [ "multi-user.target" ];
-
-        after = [ "pict-rs.service" ] ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ];
-
-        requires = lib.optionals cfg.database.createLocally [ "postgresql.service" ];
-
-        # substitute secrets and prevent others from reading the result
-        # if somehow $CREDENTIALS_DIRECTORY is not set we fail
-        preStart = mkIf (secrets != {}) ''
-          set -u
-          umask u=rw,g=,o=
-          cd "$CREDENTIALS_DIRECTORY"
-          ${utils.genJqSecretsReplacementSnippet cfg.settings substitutedConfig}
-        '';
-
-        serviceConfig = {
-          DynamicUser = true;
-          RuntimeDirectory = "lemmy";
-          ExecStart = "${cfg.server.package}/bin/lemmy_server";
-          LoadCredential = lib.foldlAttrs (acc: option: data: acc ++ [ "${option}:${toString data.path}" ]) [] secrets;
-          PrivateTmp = true;
-          MemoryDenyWriteExecute = true;
-          NoNewPrivileges = true;
-        };
-      };
-
-      systemd.services.lemmy-ui = {
-        description = "Lemmy ui";
-
-        environment = {
-          LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
-          LEMMY_UI_LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}";
-          LEMMY_UI_LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
-          LEMMY_UI_HTTPS = "false";
-          NODE_ENV = "production";
-        };
-
-        documentation = [
-          "https://join-lemmy.org/docs/en/admins/from_scratch.html"
-          "https://join-lemmy.org/docs/en/"
-        ];
-
-        wantedBy = [ "multi-user.target" ];
-
-        after = [ "lemmy.service" ];
-
-        requires = [ "lemmy.service" ];
-
-        serviceConfig = {
-          DynamicUser = true;
-          WorkingDirectory = "${cfg.ui.package}";
-          ExecStart = "${pkgs.nodejs}/bin/node ${cfg.ui.package}/dist/js/server.js";
-        };
-      };
-    };
-
-}
diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
deleted file mode 100644
index cdd60f572b99..000000000000
--- a/nixos/modules/services/web-apps/limesurvey.nix
+++ /dev/null
@@ -1,309 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-
-  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption mkPackageOption;
-  inherit (lib) literalExpression mapAttrs optional optionalString types;
-
-  cfg = config.services.limesurvey;
-  fpm = config.services.phpfpm.pools.limesurvey;
-
-  user = "limesurvey";
-  group = config.services.httpd.group;
-  stateDir = "/var/lib/limesurvey";
-
-  configType = with types; oneOf [ (attrsOf configType) str int bool ] // {
-    description = "limesurvey config type (str, int, bool or attribute set thereof)";
-  };
-
-  limesurveyConfig = pkgs.writeText "config.php" ''
-    <?php
-      return json_decode('${builtins.toJSON cfg.config}', true);
-    ?>
-  '';
-
-  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
-  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
-
-in
-{
-  # interface
-
-  options.services.limesurvey = {
-    enable = mkEnableOption "Limesurvey web application";
-
-    package = mkPackageOption pkgs "limesurvey" { };
-
-    encryptionKey = mkOption {
-      type = types.str;
-      default = "E17687FC77CEE247F0E22BB3ECF27FDE8BEC310A892347EC13013ABA11AA7EB5";
-      description = ''
-        This is a 32-byte key used to encrypt variables in the database.
-        You _must_ change this from the default value.
-      '';
-    };
-
-    encryptionNonce = mkOption {
-      type = types.str;
-      default = "1ACC8555619929DB91310BE848025A427B0F364A884FFA77";
-      description = ''
-        This is a 24-byte nonce used to encrypt variables in the database.
-        You _must_ change this from the default value.
-      '';
-    };
-
-    database = {
-      type = mkOption {
-        type = types.enum [ "mysql" "pgsql" "odbc" "mssql" ];
-        example = "pgsql";
-        default = "mysql";
-        description = "Database engine to use.";
-      };
-
-      dbEngine = mkOption {
-        type = types.enum [ "MyISAM" "InnoDB" ];
-        default = "InnoDB";
-        description = "Database storage engine to use.";
-      };
-
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Database host address.";
-      };
-
-      port = mkOption {
-        type = types.port;
-        default = if cfg.database.type == "pgsql" then 5442 else 3306;
-        defaultText = literalExpression "3306";
-        description = "Database host port.";
-      };
-
-      name = mkOption {
-        type = types.str;
-        default = "limesurvey";
-        description = "Database name.";
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "limesurvey";
-        description = "Database user.";
-      };
-
-      passwordFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        example = "/run/keys/limesurvey-dbpassword";
-        description = ''
-          A file containing the password corresponding to
-          {option}`database.user`.
-        '';
-      };
-
-      socket = mkOption {
-        type = types.nullOr types.path;
-        default =
-          if mysqlLocal then "/run/mysqld/mysqld.sock"
-          else if pgsqlLocal then "/run/postgresql"
-          else null
-        ;
-        defaultText = literalExpression "/run/mysqld/mysqld.sock";
-        description = "Path to the unix socket file to use for authentication.";
-      };
-
-      createLocally = mkOption {
-        type = types.bool;
-        default = cfg.database.type == "mysql";
-        defaultText = literalExpression "true";
-        description = ''
-          Create the database and database user locally.
-          This currently only applies if database type "mysql" is selected.
-        '';
-      };
-    };
-
-    virtualHost = mkOption {
-      type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-      example = literalExpression ''
-        {
-          hostName = "survey.example.org";
-          adminAddr = "webmaster@example.org";
-          forceSSL = true;
-          enableACME = true;
-        }
-      '';
-      description = ''
-        Apache configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
-        See [](#opt-services.httpd.virtualHosts) for further information.
-      '';
-    };
-
-    poolConfig = mkOption {
-      type = with types; attrsOf (oneOf [ str int bool ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for the LimeSurvey PHP pool. See the documentation on `php-fpm.conf`
-        for details on configuration directives.
-      '';
-    };
-
-    config = mkOption {
-      type = configType;
-      default = {};
-      description = ''
-        LimeSurvey configuration. Refer to
-        <https://manual.limesurvey.org/Optional_settings>
-        for details on supported values.
-      '';
-    };
-  };
-
-  # implementation
-
-  config = mkIf cfg.enable {
-
-    assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.type == "mysql";
-        message = "services.limesurvey.createLocally is currently only supported for database type 'mysql'";
-      }
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
-        message = "services.limesurvey.database.user must be set to ${user} if services.limesurvey.database.createLocally is set true";
-      }
-      { assertion = cfg.database.createLocally -> cfg.database.socket != null;
-        message = "services.limesurvey.database.socket must be set if services.limesurvey.database.createLocally is set to true";
-      }
-      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
-        message = "a password cannot be specified if services.limesurvey.database.createLocally is set to true";
-      }
-    ];
-
-    services.limesurvey.config = mapAttrs (name: mkDefault) {
-      runtimePath = "${stateDir}/tmp/runtime";
-      components = {
-        db = {
-          connectionString = "${cfg.database.type}:dbname=${cfg.database.name};host=${if pgsqlLocal then cfg.database.socket else cfg.database.host};port=${toString cfg.database.port}" +
-            optionalString mysqlLocal ";socket=${cfg.database.socket}";
-          username = cfg.database.user;
-          password = mkIf (cfg.database.passwordFile != null) "file_get_contents(\"${toString cfg.database.passwordFile}\");";
-          tablePrefix = "limesurvey_";
-        };
-        assetManager.basePath = "${stateDir}/tmp/assets";
-        urlManager = {
-          urlFormat = "path";
-          showScriptName = false;
-        };
-      };
-      config = {
-        tempdir = "${stateDir}/tmp";
-        uploaddir = "${stateDir}/upload";
-        encryptionnonce = cfg.encryptionNonce;
-        encryptionsecretboxkey = cfg.encryptionKey;
-        force_ssl = mkIf (cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL) "on";
-        config.defaultlang = "en";
-      };
-    };
-
-    services.mysql = mkIf mysqlLocal {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        { name = cfg.database.user;
-          ensurePermissions = {
-            "${cfg.database.name}.*" = "SELECT, CREATE, INSERT, UPDATE, DELETE, ALTER, DROP, INDEX";
-          };
-        }
-      ];
-    };
-
-    services.phpfpm.pools.limesurvey = {
-      inherit user group;
-      phpPackage = pkgs.php81;
-      phpEnv.DBENGINE = "${cfg.database.dbEngine}";
-      phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}";
-      settings = {
-        "listen.owner" = config.services.httpd.user;
-        "listen.group" = config.services.httpd.group;
-      } // cfg.poolConfig;
-    };
-
-    services.httpd = {
-      enable = true;
-      adminAddr = mkDefault cfg.virtualHost.adminAddr;
-      extraModules = [ "proxy_fcgi" ];
-      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
-        documentRoot = mkForce "${cfg.package}/share/limesurvey";
-        extraConfig = ''
-          Alias "/tmp" "${stateDir}/tmp"
-          <Directory "${stateDir}">
-            AllowOverride all
-            Require all granted
-            Options -Indexes +FollowSymlinks
-          </Directory>
-
-          Alias "/upload" "${stateDir}/upload"
-          <Directory "${stateDir}/upload">
-            AllowOverride all
-            Require all granted
-            Options -Indexes
-          </Directory>
-
-          <Directory "${cfg.package}/share/limesurvey">
-            <FilesMatch "\.php$">
-              <If "-f %{REQUEST_FILENAME}">
-                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
-              </If>
-            </FilesMatch>
-
-            AllowOverride all
-            Options -Indexes
-            DirectoryIndex index.php
-          </Directory>
-        '';
-      } ];
-    };
-
-    systemd.tmpfiles.rules = [
-      "d ${stateDir} 0750 ${user} ${group} - -"
-      "d ${stateDir}/tmp 0750 ${user} ${group} - -"
-      "d ${stateDir}/tmp/assets 0750 ${user} ${group} - -"
-      "d ${stateDir}/tmp/runtime 0750 ${user} ${group} - -"
-      "d ${stateDir}/tmp/upload 0750 ${user} ${group} - -"
-      "C ${stateDir}/upload 0750 ${user} ${group} - ${cfg.package}/share/limesurvey/upload"
-    ];
-
-    systemd.services.limesurvey-init = {
-      wantedBy = [ "multi-user.target" ];
-      before = [ "phpfpm-limesurvey.service" ];
-      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
-      environment.DBENGINE = "${cfg.database.dbEngine}";
-      environment.LIMESURVEY_CONFIG = limesurveyConfig;
-      script = ''
-        # update or install the database as required
-        ${pkgs.php81}/bin/php ${cfg.package}/share/limesurvey/application/commands/console.php updatedb || \
-        ${pkgs.php81}/bin/php ${cfg.package}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
-      '';
-      serviceConfig = {
-        User = user;
-        Group = group;
-        Type = "oneshot";
-      };
-    };
-
-    systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
-
-    users.users.${user} = {
-      group = group;
-      isSystemUser = true;
-    };
-
-  };
-}
diff --git a/nixos/modules/services/web-apps/mainsail.nix b/nixos/modules/services/web-apps/mainsail.nix
deleted file mode 100644
index cfe4c5250b55..000000000000
--- a/nixos/modules/services/web-apps/mainsail.nix
+++ /dev/null
@@ -1,61 +0,0 @@
-{ config, lib, pkgs, ... }:
-with lib;
-let
-  cfg = config.services.mainsail;
-  moonraker = config.services.moonraker;
-in
-{
-  options.services.mainsail = {
-    enable = mkEnableOption "a modern and responsive user interface for Klipper";
-
-    package = mkPackageOption pkgs "mainsail" { };
-
-    hostName = mkOption {
-      type = types.str;
-      default = "localhost";
-      description = "Hostname to serve mainsail on";
-    };
-
-    nginx = mkOption {
-      type = types.submodule
-        (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
-      default = { };
-      example = literalExpression ''
-        {
-          serverAliases = [ "mainsail.''${config.networking.domain}" ];
-        }
-      '';
-      description = "Extra configuration for the nginx virtual host of mainsail.";
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.nginx = {
-      enable = true;
-      upstreams.mainsail-apiserver.servers."${moonraker.address}:${toString moonraker.port}" = { };
-      virtualHosts."${cfg.hostName}" = mkMerge [
-        cfg.nginx
-        {
-          root = mkForce "${cfg.package}/share/mainsail";
-          locations = {
-            "/" = {
-              index = "index.html";
-              tryFiles = "$uri $uri/ /index.html";
-            };
-            "/index.html".extraConfig = ''
-              add_header Cache-Control "no-store, no-cache, must-revalidate";
-            '';
-            "/websocket" = {
-              proxyWebsockets = true;
-              proxyPass = "http://mainsail-apiserver/websocket";
-            };
-            "~ ^/(printer|api|access|machine|server)/" = {
-              proxyWebsockets = true;
-              proxyPass = "http://mainsail-apiserver$request_uri";
-            };
-          };
-        }
-      ];
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
deleted file mode 100644
index daebd6441cb5..000000000000
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ /dev/null
@@ -1,917 +0,0 @@
-{ lib, pkgs, config, options, ... }:
-
-let
-  cfg = config.services.mastodon;
-  opt = options.services.mastodon;
-
-  # We only want to create a Redis and PostgreSQL databases if we're actually going to connect to it local.
-  redisActuallyCreateLocally = cfg.redis.createLocally && (cfg.redis.host == "127.0.0.1" || cfg.redis.enableUnixSocket);
-  databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "/run/postgresql";
-
-  env = {
-    RAILS_ENV = "production";
-    NODE_ENV = "production";
-
-    LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
-
-    # mastodon-web concurrency.
-    WEB_CONCURRENCY = toString cfg.webProcesses;
-    MAX_THREADS = toString cfg.webThreads;
-
-    DB_USER = cfg.database.user;
-
-    DB_HOST = cfg.database.host;
-    DB_NAME = cfg.database.name;
-    LOCAL_DOMAIN = cfg.localDomain;
-    SMTP_SERVER = cfg.smtp.host;
-    SMTP_PORT = toString(cfg.smtp.port);
-    SMTP_FROM_ADDRESS = cfg.smtp.fromAddress;
-    PAPERCLIP_ROOT_PATH = "/var/lib/mastodon/public-system";
-    PAPERCLIP_ROOT_URL = "/system";
-    ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false";
-
-    TRUSTED_PROXY_IP = cfg.trustedProxy;
-  }
-  // lib.optionalAttrs (cfg.redis.host != null) { REDIS_HOST = cfg.redis.host; }
-  // lib.optionalAttrs (cfg.redis.port != null) { REDIS_PORT = toString(cfg.redis.port); }
-  // lib.optionalAttrs (cfg.redis.createLocally && cfg.redis.enableUnixSocket) { REDIS_URL = "unix://${config.services.redis.servers.mastodon.unixSocket}"; }
-  // lib.optionalAttrs (cfg.database.host != "/run/postgresql" && cfg.database.port != null) { DB_PORT = toString cfg.database.port; }
-  // lib.optionalAttrs cfg.smtp.authenticate { SMTP_LOGIN  = cfg.smtp.user; }
-  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_HOST = cfg.elasticsearch.host; }
-  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PORT = toString(cfg.elasticsearch.port); }
-  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PRESET = cfg.elasticsearch.preset; }
-  // lib.optionalAttrs (cfg.elasticsearch.user != null) { ES_USER = cfg.elasticsearch.user; }
-  // cfg.extraConfig;
-
-  systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@mount" "@obsolete" "@privileged" "@setuid" ];
-
-  cfgService = {
-    # User and group
-    User = cfg.user;
-    Group = cfg.group;
-    # Working directory
-    WorkingDirectory = cfg.package;
-    # State directory and mode
-    StateDirectory = "mastodon";
-    StateDirectoryMode = "0750";
-    # Logs directory and mode
-    LogsDirectory = "mastodon";
-    LogsDirectoryMode = "0750";
-    # Proc filesystem
-    ProcSubset = "pid";
-    ProtectProc = "invisible";
-    # Access write directories
-    UMask = "0027";
-    # Capabilities
-    CapabilityBoundingSet = "";
-    # Security
-    NoNewPrivileges = true;
-    # Sandboxing
-    ProtectSystem = "strict";
-    ProtectHome = true;
-    PrivateTmp = true;
-    PrivateDevices = true;
-    PrivateUsers = true;
-    ProtectClock = true;
-    ProtectHostname = true;
-    ProtectKernelLogs = true;
-    ProtectKernelModules = true;
-    ProtectKernelTunables = true;
-    ProtectControlGroups = true;
-    RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
-    RestrictNamespaces = true;
-    LockPersonality = true;
-    MemoryDenyWriteExecute = false;
-    RestrictRealtime = true;
-    RestrictSUIDSGID = true;
-    RemoveIPC = true;
-    PrivateMounts = true;
-    # System Call Filtering
-    SystemCallArchitectures = "native";
-  };
-
-  # Services that all Mastodon units After= and Requires= on
-  commonServices = lib.optional redisActuallyCreateLocally "redis-mastodon.service"
-    ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
-    ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
-
-  envFile = pkgs.writeText "mastodon.env" (lib.concatMapStrings (s: s + "\n") (
-    (lib.concatLists (lib.mapAttrsToList (name: value:
-      lib.optional (value != null) ''${name}="${toString value}"''
-    ) env))));
-
-  mastodonTootctl = let
-    sourceExtraEnv = lib.concatMapStrings (p: "source ${p}\n") cfg.extraEnvFiles;
-  in pkgs.writeShellScriptBin "mastodon-tootctl" ''
-    set -a
-    export RAILS_ROOT="${cfg.package}"
-    source "${envFile}"
-    source /var/lib/mastodon/.secrets_env
-    ${sourceExtraEnv}
-
-    sudo=exec
-    if [[ "$USER" != ${cfg.user} ]]; then
-      sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env'
-    fi
-    $sudo ${cfg.package}/bin/tootctl "$@"
-  '';
-
-  sidekiqUnits = lib.attrsets.mapAttrs' (name: processCfg:
-    lib.nameValuePair "mastodon-sidekiq-${name}" (let
-      jobClassArgs = toString (builtins.map (c: "-q ${c}") processCfg.jobClasses);
-      jobClassLabel = toString ([""] ++ processCfg.jobClasses);
-      threads = toString (if processCfg.threads == null then cfg.sidekiqThreads else processCfg.threads);
-    in {
-      after = [ "network.target" "mastodon-init-dirs.service" ] ++ commonServices;
-      requires = [ "mastodon-init-dirs.service" ] ++ commonServices;
-      description = "Mastodon sidekiq${jobClassLabel}";
-      wantedBy = [ "mastodon.target" ];
-      environment = env // {
-        PORT = toString(cfg.sidekiqPort);
-        DB_POOL = threads;
-      };
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/sidekiq ${jobClassArgs} -c ${threads} -r ${cfg.package}";
-        Restart = "always";
-        RestartSec = 20;
-        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
-        WorkingDirectory = cfg.package;
-        LimitNOFILE = "1024000";
-        # System Call Filtering
-        SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
-      } // cfgService;
-      path = with pkgs; [ ffmpeg-headless file imagemagick ];
-    })
-  ) cfg.sidekiqProcesses;
-
-  streamingUnits = builtins.listToAttrs
-      (map (i: {
-        name = "mastodon-streaming-${toString i}";
-        value = {
-          after = [ "network.target" "mastodon-init-dirs.service" ] ++ commonServices;
-          requires = [ "mastodon-init-dirs.service" ] ++ commonServices;
-          wantedBy = [ "mastodon.target" "mastodon-streaming.target" ];
-          description = "Mastodon streaming ${toString i}";
-          environment = env // { SOCKET = "/run/mastodon-streaming/streaming-${toString i}.socket"; };
-          serviceConfig = {
-            ExecStart = "${cfg.package}/run-streaming.sh";
-            Restart = "always";
-            RestartSec = 20;
-            EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
-            WorkingDirectory = cfg.package;
-            # Runtime directory and mode
-            RuntimeDirectory = "mastodon-streaming";
-            RuntimeDirectoryMode = "0750";
-            # System Call Filtering
-            SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@memlock" "@resources" ])) "pipe" "pipe2" ];
-          } // cfgService;
-        };
-      })
-      (lib.range 1 cfg.streamingProcesses));
-
-in {
-
-  imports = [
-    (lib.mkRemovedOptionModule
-      [ "services" "mastodon" "streamingPort" ]
-      "Mastodon currently doesn't support streaming via TCP ports. Please open a PR if you need this."
-    )
-  ];
-
-  options = {
-    services.mastodon = {
-      enable = lib.mkEnableOption "Mastodon, a federated social network server";
-
-      configureNginx = lib.mkOption {
-        description = ''
-          Configure nginx as a reverse proxy for mastodon.
-          Note that this makes some assumptions on your setup, and sets settings that will
-          affect other virtualHosts running on your nginx instance, if any.
-          Alternatively you can configure a reverse-proxy of your choice to serve these paths:
-
-          `/ -> $(nix-instantiate --eval '<nixpkgs>' -A mastodon.outPath)/public`
-
-          `/ -> 127.0.0.1:{{ webPort }} `(If there was no file in the directory above.)
-
-          `/system/ -> /var/lib/mastodon/public-system/`
-
-          `/api/v1/streaming/ -> 127.0.0.1:{{ streamingPort }}`
-
-          Make sure that websockets are forwarded properly. You might want to set up caching
-          of some requests. Take a look at mastodon's provided nginx configuration at
-          `https://github.com/mastodon/mastodon/blob/master/dist/nginx.conf`.
-        '';
-        type = lib.types.bool;
-        default = false;
-      };
-
-      user = lib.mkOption {
-        description = ''
-          User under which mastodon runs. If it is set to "mastodon",
-          that user will be created, otherwise it should be set to the
-          name of a user created elsewhere.
-          In both cases, the `mastodon` package will be added to the user's package set
-          and a tootctl wrapper to system packages that switches to the configured account
-          and load the right environment.
-        '';
-        type = lib.types.str;
-        default = "mastodon";
-      };
-
-      group = lib.mkOption {
-        description = ''
-          Group under which mastodon runs.
-        '';
-        type = lib.types.str;
-        default = "mastodon";
-      };
-
-      streamingProcesses = lib.mkOption {
-        description = ''
-          Number of processes used by the mastodon-streaming service.
-          Please define this explicitly, recommended is the amount of your CPU cores minus one.
-        '';
-        type = lib.types.ints.positive;
-        example = 3;
-      };
-
-      webPort = lib.mkOption {
-        description = "TCP port used by the mastodon-web service.";
-        type = lib.types.port;
-        default = 55001;
-      };
-      webProcesses = lib.mkOption {
-        description = "Processes used by the mastodon-web service.";
-        type = lib.types.int;
-        default = 2;
-      };
-      webThreads = lib.mkOption {
-        description = "Threads per process used by the mastodon-web service.";
-        type = lib.types.int;
-        default = 5;
-      };
-
-      sidekiqPort = lib.mkOption {
-        description = "TCP port used by the mastodon-sidekiq service.";
-        type = lib.types.port;
-        default = 55002;
-      };
-
-      sidekiqThreads = lib.mkOption {
-        description = "Worker threads used by the mastodon-sidekiq-all service. If `sidekiqProcesses` is configured and any processes specify null `threads`, this value is used.";
-        type = lib.types.int;
-        default = 25;
-      };
-
-      sidekiqProcesses = lib.mkOption {
-        description = "How many Sidekiq processes should be used to handle background jobs, and which job classes they handle. *Read the [upstream documentation](https://docs.joinmastodon.org/admin/scaling/#sidekiq) before configuring this!*";
-        type = with lib.types; attrsOf (submodule {
-          options = {
-            jobClasses = lib.mkOption {
-              type = listOf (enum [ "default" "push" "pull" "mailers" "scheduler" "ingress" ]);
-              description = "If not empty, which job classes should be executed by this process. *Only one process should handle the 'scheduler' class. If left empty, this process will handle the 'scheduler' class.*";
-            };
-            threads = lib.mkOption {
-              type = nullOr int;
-              description = "Number of threads this process should use for executing jobs. If null, the configured `sidekiqThreads` are used.";
-            };
-          };
-        });
-        default = {
-          all = {
-            jobClasses = [ ];
-            threads = null;
-          };
-        };
-        example = {
-          all = {
-            jobClasses = [ ];
-            threads = null;
-          };
-          ingress = {
-            jobClasses = [ "ingress" ];
-            threads = 5;
-          };
-          default = {
-            jobClasses = [ "default" ];
-            threads = 10;
-          };
-          push-pull = {
-            jobClasses = [ "push" "pull" ];
-            threads = 5;
-          };
-        };
-      };
-
-      vapidPublicKeyFile = lib.mkOption {
-        description = ''
-          Path to file containing the public key used for Web Push
-          Voluntary Application Server Identification.  A new keypair can
-          be generated by running:
-
-          `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys`
-
-          If {option}`mastodon.vapidPrivateKeyFile`does not
-          exist, it and this file will be created with a new keypair.
-        '';
-        default = "/var/lib/mastodon/secrets/vapid-public-key";
-        type = lib.types.str;
-      };
-
-      localDomain = lib.mkOption {
-        description = "The domain serving your Mastodon instance.";
-        example = "social.example.org";
-        type = lib.types.str;
-      };
-
-      secretKeyBaseFile = lib.mkOption {
-        description = ''
-          Path to file containing the secret key base.
-          A new secret key base can be generated by running:
-
-          `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret`
-
-          If this file does not exist, it will be created with a new secret key base.
-        '';
-        default = "/var/lib/mastodon/secrets/secret-key-base";
-        type = lib.types.str;
-      };
-
-      otpSecretFile = lib.mkOption {
-        description = ''
-          Path to file containing the OTP secret.
-          A new OTP secret can be generated by running:
-
-          `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret`
-
-          If this file does not exist, it will be created with a new OTP secret.
-        '';
-        default = "/var/lib/mastodon/secrets/otp-secret";
-        type = lib.types.str;
-      };
-
-      vapidPrivateKeyFile = lib.mkOption {
-        description = ''
-          Path to file containing the private key used for Web Push
-          Voluntary Application Server Identification.  A new keypair can
-          be generated by running:
-
-          `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys`
-
-          If this file does not exist, it will be created with a new
-          private key.
-        '';
-        default = "/var/lib/mastodon/secrets/vapid-private-key";
-        type = lib.types.str;
-      };
-
-      trustedProxy = lib.mkOption {
-        description = ''
-          You need to set it to the IP from which your reverse proxy sends requests to Mastodon's web process,
-          otherwise Mastodon will record the reverse proxy's own IP as the IP of all requests, which would be
-          bad because IP addresses are used for important rate limits and security functions.
-        '';
-        type = lib.types.str;
-        default = "127.0.0.1";
-      };
-
-      enableUnixSocket = lib.mkOption {
-        description = ''
-          Instead of binding to an IP address like 127.0.0.1, you may bind to a Unix socket. This variable
-          is process-specific, e.g. you need different values for every process, and it works for both web (Puma)
-          processes and streaming API (Node.js) processes.
-        '';
-        type = lib.types.bool;
-        default = true;
-      };
-
-      redis = {
-        createLocally = lib.mkOption {
-          description = "Configure local Redis server for Mastodon.";
-          type = lib.types.bool;
-          default = true;
-        };
-
-        host = lib.mkOption {
-          description = "Redis host.";
-          type = lib.types.nullOr lib.types.str;
-          default = if cfg.redis.createLocally && !cfg.redis.enableUnixSocket then "127.0.0.1" else null;
-          defaultText = lib.literalExpression ''
-            if config.${opt.redis.createLocally} && !config.${opt.redis.enableUnixSocket} then "127.0.0.1" else null
-          '';
-        };
-
-        port = lib.mkOption {
-          description = "Redis port.";
-          type = lib.types.nullOr lib.types.port;
-          default = if cfg.redis.createLocally && !cfg.redis.enableUnixSocket then 31637 else null;
-          defaultText = lib.literalExpression ''
-            if config.${opt.redis.createLocally} && !config.${opt.redis.enableUnixSocket} then 31637 else null
-          '';
-        };
-
-        passwordFile = lib.mkOption {
-          description = "A file containing the password for Redis database.";
-          type = lib.types.nullOr lib.types.path;
-          default = null;
-          example = "/run/keys/mastodon-redis-password";
-        };
-
-        enableUnixSocket = lib.mkOption {
-          description = "Use Unix socket";
-          type = lib.types.bool;
-          default = true;
-        };
-      };
-
-      database = {
-        createLocally = lib.mkOption {
-          description = "Configure local PostgreSQL database server for Mastodon.";
-          type = lib.types.bool;
-          default = true;
-        };
-
-        host = lib.mkOption {
-          type = lib.types.str;
-          default = "/run/postgresql";
-          example = "192.168.23.42";
-          description = "Database host address or unix socket.";
-        };
-
-        port = lib.mkOption {
-          type = lib.types.nullOr lib.types.port;
-          default = if cfg.database.createLocally then null else 5432;
-          defaultText = lib.literalExpression ''
-            if config.${opt.database.createLocally}
-            then null
-            else 5432
-          '';
-          description = "Database host port.";
-        };
-
-        name = lib.mkOption {
-          type = lib.types.str;
-          default = "mastodon";
-          description = "Database name.";
-        };
-
-        user = lib.mkOption {
-          type = lib.types.str;
-          default = "mastodon";
-          description = "Database user.";
-        };
-
-        passwordFile = lib.mkOption {
-          type = lib.types.nullOr lib.types.path;
-          default = null;
-          example = "/var/lib/mastodon/secrets/db-password";
-          description = ''
-            A file containing the password corresponding to
-            {option}`database.user`.
-          '';
-        };
-      };
-
-      smtp = {
-        createLocally = lib.mkOption {
-          description = "Configure local Postfix SMTP server for Mastodon.";
-          type = lib.types.bool;
-          default = true;
-        };
-
-        authenticate = lib.mkOption {
-          description = "Authenticate with the SMTP server using username and password.";
-          type = lib.types.bool;
-          default = false;
-        };
-
-        host = lib.mkOption {
-          description = "SMTP host used when sending emails to users.";
-          type = lib.types.str;
-          default = "127.0.0.1";
-        };
-
-        port = lib.mkOption {
-          description = "SMTP port used when sending emails to users.";
-          type = lib.types.port;
-          default = 25;
-        };
-
-        fromAddress = lib.mkOption {
-          description = ''"From" address used when sending Emails to users.'';
-          type = lib.types.str;
-        };
-
-        user = lib.mkOption {
-          type = lib.types.nullOr lib.types.str;
-          default = null;
-          example = "mastodon@example.com";
-          description = "SMTP login name.";
-        };
-
-        passwordFile = lib.mkOption {
-          type = lib.types.nullOr lib.types.path;
-          default = null;
-          example = "/var/lib/mastodon/secrets/smtp-password";
-          description = ''
-            Path to file containing the SMTP password.
-          '';
-        };
-      };
-
-      elasticsearch = {
-        host = lib.mkOption {
-          description = ''
-            Elasticsearch host.
-            If it is not null, Elasticsearch full text search will be enabled.
-          '';
-          type = lib.types.nullOr lib.types.str;
-          default = null;
-        };
-
-        port = lib.mkOption {
-          description = "Elasticsearch port.";
-          type = lib.types.port;
-          default = 9200;
-        };
-
-        preset = lib.mkOption {
-          description = ''
-            It controls the ElasticSearch indices configuration (number of shards and replica).
-          '';
-          type = lib.types.enum [ "single_node_cluster" "small_cluster" "large_cluster" ];
-          default = "single_node_cluster";
-          example = "large_cluster";
-        };
-
-        user = lib.mkOption {
-          description = "Used for optionally authenticating with Elasticsearch.";
-          type = lib.types.nullOr lib.types.str;
-          default = null;
-          example = "elasticsearch-mastodon";
-        };
-
-        passwordFile = lib.mkOption {
-          description = ''
-            Path to file containing password for optionally authenticating with Elasticsearch.
-          '';
-          type = lib.types.nullOr lib.types.path;
-          default = null;
-          example = "/var/lib/mastodon/secrets/elasticsearch-password";
-        };
-      };
-
-      package = lib.mkOption {
-        type = lib.types.package;
-        default = pkgs.mastodon;
-        defaultText = lib.literalExpression "pkgs.mastodon";
-        description = "Mastodon package to use.";
-      };
-
-      extraConfig = lib.mkOption {
-        type = lib.types.attrs;
-        default = {};
-        description = ''
-          Extra environment variables to pass to all mastodon services.
-        '';
-      };
-
-      extraEnvFiles = lib.mkOption {
-        type = with lib.types; listOf path;
-        default = [];
-        description = ''
-          Extra environment files to pass to all mastodon services. Useful for passing down environmental secrets.
-        '';
-        example = [ "/etc/mastodon/s3config.env" ];
-      };
-
-      automaticMigrations = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = ''
-          Do automatic database migrations.
-        '';
-      };
-
-      mediaAutoRemove = {
-        enable = lib.mkOption {
-          type = lib.types.bool;
-          default = true;
-          example = false;
-          description = ''
-            Automatically remove remote media attachments and preview cards older than the configured amount of days.
-
-            Recommended in https://docs.joinmastodon.org/admin/setup/.
-          '';
-        };
-
-        startAt = lib.mkOption {
-          type = lib.types.str;
-          default = "daily";
-          example = "hourly";
-          description = ''
-            How often to remove remote media.
-
-            The format is described in {manpage}`systemd.time(7)`.
-          '';
-        };
-
-        olderThanDays = lib.mkOption {
-          type = lib.types.int;
-          default = 30;
-          example = 14;
-          description = ''
-            How old remote media needs to be in order to be removed.
-          '';
-        };
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable (lib.mkMerge [{
-    assertions = [
-      {
-        assertion = !redisActuallyCreateLocally -> (cfg.redis.host != "127.0.0.1" && cfg.redis.port != null);
-        message = ''
-          `services.mastodon.redis.host` and `services.mastodon.redis.port` need to be set if
-            `services.mastodon.redis.createLocally` is not enabled.
-        '';
-      }
-      {
-        assertion = redisActuallyCreateLocally -> (!cfg.redis.enableUnixSocket || (cfg.redis.host == null && cfg.redis.port == null));
-        message = ''
-          `services.mastodon.redis.enableUnixSocket` needs to be disabled if
-            `services.mastodon.redis.host` and `services.mastodon.redis.port` is used.
-        '';
-      }
-      {
-        assertion = redisActuallyCreateLocally -> (!cfg.redis.enableUnixSocket || cfg.redis.passwordFile == null);
-        message = ''
-          <option>services.mastodon.redis.enableUnixSocket</option> needs to be disabled if
-            <option>services.mastodon.redis.passwordFile</option> is used.
-        '';
-      }
-      {
-        assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user && cfg.database.user == cfg.database.name);
-        message = ''
-          For local automatic database provisioning (services.mastodon.database.createLocally == true) with peer
-            authentication (services.mastodon.database.host == "/run/postgresql") to work services.mastodon.user
-            and services.mastodon.database.user must be identical.
-        '';
-      }
-      {
-        assertion = !databaseActuallyCreateLocally -> (cfg.database.host != "/run/postgresql");
-        message = ''
-          <option>services.mastodon.database.host</option> needs to be set if
-            <option>services.mastodon.database.createLocally</option> is not enabled.
-        '';
-      }
-      {
-        assertion = cfg.smtp.authenticate -> (cfg.smtp.user != null);
-        message = ''
-          <option>services.mastodon.smtp.user</option> needs to be set if
-            <option>services.mastodon.smtp.authenticate</option> is enabled.
-        '';
-      }
-      {
-        assertion = cfg.smtp.authenticate -> (cfg.smtp.passwordFile != null);
-        message = ''
-          <option>services.mastodon.smtp.passwordFile</option> needs to be set if
-            <option>services.mastodon.smtp.authenticate</option> is enabled.
-        '';
-      }
-      {
-        assertion = 1 ==
-          (lib.count (x: x)
-            (lib.mapAttrsToList
-              (_: v: builtins.elem "scheduler" v.jobClasses || v.jobClasses == [ ])
-              cfg.sidekiqProcesses));
-        message = "There must be exactly one Sidekiq queue in services.mastodon.sidekiqProcesses with jobClass \"scheduler\".";
-      }
-    ];
-
-    environment.systemPackages = [ mastodonTootctl ];
-
-    systemd.targets.mastodon = {
-      description = "Target for all Mastodon services";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-    };
-
-    systemd.targets.mastodon-streaming = {
-      description = "Target for all Mastodon streaming services";
-      wantedBy = [ "multi-user.target" "mastodon.target" ];
-      after = [ "network.target" ];
-    };
-
-    systemd.services.mastodon-init-dirs = {
-      script = ''
-        umask 077
-
-        if ! test -f ${cfg.secretKeyBaseFile}; then
-          mkdir -p $(dirname ${cfg.secretKeyBaseFile})
-          bin/rake secret > ${cfg.secretKeyBaseFile}
-        fi
-        if ! test -f ${cfg.otpSecretFile}; then
-          mkdir -p $(dirname ${cfg.otpSecretFile})
-          bin/rake secret > ${cfg.otpSecretFile}
-        fi
-        if ! test -f ${cfg.vapidPrivateKeyFile}; then
-          mkdir -p $(dirname ${cfg.vapidPrivateKeyFile}) $(dirname ${cfg.vapidPublicKeyFile})
-          keypair=$(bin/rake webpush:generate_keys)
-          echo $keypair | grep --only-matching "Private -> [^ ]\+" | sed 's/^Private -> //' > ${cfg.vapidPrivateKeyFile}
-          echo $keypair | grep --only-matching "Public -> [^ ]\+" | sed 's/^Public -> //' > ${cfg.vapidPublicKeyFile}
-        fi
-
-        cat > /var/lib/mastodon/.secrets_env <<EOF
-        SECRET_KEY_BASE="$(cat ${cfg.secretKeyBaseFile})"
-        OTP_SECRET="$(cat ${cfg.otpSecretFile})"
-        VAPID_PRIVATE_KEY="$(cat ${cfg.vapidPrivateKeyFile})"
-        VAPID_PUBLIC_KEY="$(cat ${cfg.vapidPublicKeyFile})"
-      '' + lib.optionalString (cfg.redis.passwordFile != null)''
-        REDIS_PASSWORD="$(cat ${cfg.redis.passwordFile})"
-      '' + lib.optionalString (cfg.database.passwordFile != null) ''
-        DB_PASS="$(cat ${cfg.database.passwordFile})"
-      '' + lib.optionalString cfg.smtp.authenticate ''
-        SMTP_PASSWORD="$(cat ${cfg.smtp.passwordFile})"
-      '' + lib.optionalString (cfg.elasticsearch.passwordFile != null) ''
-        ES_PASS="$(cat ${cfg.elasticsearch.passwordFile})"
-      '' + ''
-        EOF
-      '';
-      environment = env;
-      serviceConfig = {
-        Type = "oneshot";
-        SyslogIdentifier = "mastodon-init-dirs";
-        # System Call Filtering
-        SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ])) "@chown" "pipe" "pipe2" ];
-      } // cfgService;
-
-      after = [ "network.target" ];
-    };
-
-    systemd.services.mastodon-init-db = lib.mkIf cfg.automaticMigrations {
-      script = lib.optionalString (!databaseActuallyCreateLocally) ''
-        umask 077
-        export PGPASSWORD="$(cat '${cfg.database.passwordFile}')"
-      '' + ''
-        result="$(psql -t --csv -c \
-            "select count(*) from pg_class c \
-            join pg_namespace s on s.oid = c.relnamespace \
-            where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \
-            and s.nspname not like 'pg_temp%';")" || error_code=$?
-        if [ "''${error_code:-0}" -ne 0 ]; then
-          echo "Failure checking if database is seeded. psql gave exit code $error_code"
-          exit "$error_code"
-        fi
-        if [ "$result" -eq 0 ]; then
-          echo "Seeding database"
-          SAFETY_ASSURED=1 rails db:schema:load
-          rails db:seed
-        else
-          echo "Migrating database (this might be a noop)"
-          rails db:migrate
-        fi
-      '' +  lib.optionalString (!databaseActuallyCreateLocally) ''
-        unset PGPASSWORD
-      '';
-      path = [ cfg.package pkgs.postgresql ];
-      environment = env // lib.optionalAttrs (!databaseActuallyCreateLocally) {
-        PGHOST = cfg.database.host;
-        PGPORT = toString cfg.database.port;
-        PGDATABASE = cfg.database.name;
-        PGUSER = cfg.database.user;
-      };
-      serviceConfig = {
-        Type = "oneshot";
-        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
-        WorkingDirectory = cfg.package;
-        # System Call Filtering
-        SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ])) "@chown" "pipe" "pipe2" ];
-      } // cfgService;
-      after = [ "network.target" "mastodon-init-dirs.service" ]
-        ++ lib.optional databaseActuallyCreateLocally "postgresql.service";
-      requires = [ "mastodon-init-dirs.service" ]
-        ++ lib.optional databaseActuallyCreateLocally "postgresql.service";
-    };
-
-    systemd.services.mastodon-web = {
-      after = [ "network.target" "mastodon-init-dirs.service" ] ++ commonServices;
-      requires = [ "mastodon-init-dirs.service" ] ++ commonServices;
-      wantedBy = [ "mastodon.target" ];
-      description = "Mastodon web";
-      environment = env // (if cfg.enableUnixSocket
-        then { SOCKET = "/run/mastodon-web/web.socket"; }
-        else { PORT = toString(cfg.webPort); }
-      );
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/puma -C config/puma.rb";
-        Restart = "always";
-        RestartSec = 20;
-        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
-        WorkingDirectory = cfg.package;
-        # Runtime directory and mode
-        RuntimeDirectory = "mastodon-web";
-        RuntimeDirectoryMode = "0750";
-        # System Call Filtering
-        SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
-      } // cfgService;
-      path = with pkgs; [ ffmpeg-headless file imagemagick ];
-    };
-
-    systemd.services.mastodon-media-auto-remove = lib.mkIf cfg.mediaAutoRemove.enable {
-      description = "Mastodon media auto remove";
-      environment = env;
-      serviceConfig = {
-        Type = "oneshot";
-        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
-      } // cfgService;
-      script = let
-        olderThanDays = toString cfg.mediaAutoRemove.olderThanDays;
-      in ''
-        ${cfg.package}/bin/tootctl media remove --days=${olderThanDays}
-        ${cfg.package}/bin/tootctl preview_cards remove --days=${olderThanDays}
-      '';
-      startAt = cfg.mediaAutoRemove.startAt;
-    };
-
-    services.nginx = lib.mkIf cfg.configureNginx {
-      enable = true;
-      recommendedProxySettings = true; # required for redirections to work
-      virtualHosts."${cfg.localDomain}" = {
-        root = "${cfg.package}/public/";
-        # mastodon only supports https, but you can override this if you offload tls elsewhere.
-        forceSSL = lib.mkDefault true;
-        enableACME = lib.mkDefault true;
-
-        locations."/system/".alias = "/var/lib/mastodon/public-system/";
-
-        locations."/" = {
-          tryFiles = "$uri @proxy";
-        };
-
-        locations."@proxy" = {
-          proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-web/web.socket" else "http://127.0.0.1:${toString(cfg.webPort)}");
-          proxyWebsockets = true;
-        };
-
-        locations."/api/v1/streaming/" = {
-          proxyPass = "http://mastodon-streaming";
-          proxyWebsockets = true;
-        };
-      };
-      upstreams.mastodon-streaming = {
-        extraConfig = ''
-          least_conn;
-        '';
-        servers = builtins.listToAttrs
-          (map (i: {
-            name = "unix:/run/mastodon-streaming/streaming-${toString i}.socket";
-            value = { };
-          }) (lib.range 1 cfg.streamingProcesses));
-      };
-    };
-
-    services.postfix = lib.mkIf (cfg.smtp.createLocally && cfg.smtp.host == "127.0.0.1") {
-      enable = true;
-      hostname = lib.mkDefault "${cfg.localDomain}";
-    };
-    services.redis.servers.mastodon = lib.mkIf redisActuallyCreateLocally (lib.mkMerge [
-      {
-        enable = true;
-      }
-      (lib.mkIf (!cfg.redis.enableUnixSocket) {
-        port = cfg.redis.port;
-      })
-    ]);
-    services.postgresql = lib.mkIf databaseActuallyCreateLocally {
-      enable = true;
-      ensureUsers = [
-        {
-          name = cfg.database.name;
-          ensureDBOwnership = true;
-        }
-      ];
-      ensureDatabases = [ cfg.database.name ];
-    };
-
-    users.users = lib.mkMerge [
-      (lib.mkIf (cfg.user == "mastodon") {
-        mastodon = {
-          isSystemUser = true;
-          home = cfg.package;
-          inherit (cfg) group;
-        };
-      })
-      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package pkgs.imagemagick ])
-      (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {${config.services.mastodon.user}.extraGroups = [ "redis-mastodon" ];})
-    ];
-
-    users.groups.${cfg.group}.members = lib.optional cfg.configureNginx config.services.nginx.user;
-  }
-  { systemd.services = lib.mkMerge [ sidekiqUnits streamingUnits ]; }
-  ]);
-
-  meta.maintainers = with lib.maintainers; [ happy-river erictapen ];
-
-}
diff --git a/nixos/modules/services/web-apps/matomo.md b/nixos/modules/services/web-apps/matomo.md
deleted file mode 100644
index e750c0c14775..000000000000
--- a/nixos/modules/services/web-apps/matomo.md
+++ /dev/null
@@ -1,77 +0,0 @@
-# Matomo {#module-services-matomo}
-
-Matomo is a real-time web analytics application. This module configures
-php-fpm as backend for Matomo, optionally configuring an nginx vhost as well.
-
-An automatic setup is not supported by Matomo, so you need to configure Matomo
-itself in the browser-based Matomo setup.
-
-## Database Setup {#module-services-matomo-database-setup}
-
-You also need to configure a MariaDB or MySQL database and -user for Matomo
-yourself, and enter those credentials in your browser. You can use
-passwordless database authentication via the UNIX_SOCKET authentication
-plugin with the following SQL commands:
-```
-# For MariaDB
-INSTALL PLUGIN unix_socket SONAME 'auth_socket';
-CREATE DATABASE matomo;
-CREATE USER 'matomo'@'localhost' IDENTIFIED WITH unix_socket;
-GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
-
-# For MySQL
-INSTALL PLUGIN auth_socket SONAME 'auth_socket.so';
-CREATE DATABASE matomo;
-CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket;
-GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
-```
-Then fill in `matomo` as database user and database name,
-and leave the password field blank. This authentication works by allowing
-only the `matomo` unix user to authenticate as the
-`matomo` database user (without needing a password), but no
-other users. For more information on passwordless login, see
-<https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/>.
-
-Of course, you can use password based authentication as well, e.g. when the
-database is not on the same host.
-
-## Archive Processing {#module-services-matomo-archive-processing}
-
-This module comes with the systemd service
-`matomo-archive-processing.service` and a timer that
-automatically triggers archive processing every hour. This means that you
-can safely
-[disable browser triggers for Matomo archiving](
-https://matomo.org/docs/setup-auto-archiving/#disable-browser-triggers-for-matomo-archiving-and-limit-matomo-reports-to-updating-every-hour
-) at
-`Administration > System > General Settings`.
-
-With automatic archive processing, you can now also enable to
-[delete old visitor logs](https://matomo.org/docs/privacy/#step-2-delete-old-visitors-logs)
-at `Administration > System > Privacy`, but make sure that you run `systemctl start
-matomo-archive-processing.service` at least once without errors if
-you have already collected data before, so that the reports get archived
-before the source data gets deleted.
-
-## Backup {#module-services-matomo-backups}
-
-You only need to take backups of your MySQL database and the
-{file}`/var/lib/matomo/config/config.ini.php` file. Use a user
-in the `matomo` group or root to access the file. For more
-information, see
-<https://matomo.org/faq/how-to-install/faq_138/>.
-
-## Issues {#module-services-matomo-issues}
-
-  - Matomo will warn you that the JavaScript tracker is not writable. This is
-    because it's located in the read-only nix store. You can safely ignore
-    this, unless you need a plugin that needs JavaScript tracker access.
-
-## Using other Web Servers than nginx {#module-services-matomo-other-web-servers}
-
-You can use other web servers by forwarding calls for
-{file}`index.php` and {file}`piwik.php` to the
-[`services.phpfpm.pools.<name>.socket`](#opt-services.phpfpm.pools._name_.socket)
-fastcgi unix socket. You can use
-the nginx configuration in the module code as a reference to what else
-should be configured.
diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix
deleted file mode 100644
index 722745dbdb5d..000000000000
--- a/nixos/modules/services/web-apps/matomo.nix
+++ /dev/null
@@ -1,322 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-with lib;
-let
-  cfg = config.services.matomo;
-  fpm = config.services.phpfpm.pools.${pool};
-
-  user = "matomo";
-  dataDir = "/var/lib/${user}";
-  deprecatedDataDir = "/var/lib/piwik";
-
-  pool = user;
-  phpExecutionUnit = "phpfpm-${pool}";
-  databaseService = "mysql.service";
-
-in {
-  imports = [
-    (mkRenamedOptionModule [ "services" "piwik" "enable" ] [ "services" "matomo" "enable" ])
-    (mkRenamedOptionModule [ "services" "piwik" "webServerUser" ] [ "services" "matomo" "webServerUser" ])
-    (mkRemovedOptionModule [ "services" "piwik" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings")
-    (mkRemovedOptionModule [ "services" "matomo" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings")
-    (mkRenamedOptionModule [ "services" "piwik" "nginx" ] [ "services" "matomo" "nginx" ])
-    (mkRenamedOptionModule [ "services" "matomo" "periodicArchiveProcessingUrl" ] [ "services" "matomo" "hostname" ])
-  ];
-
-  options = {
-    services.matomo = {
-      # NixOS PR for database setup: https://github.com/NixOS/nixpkgs/pull/6963
-      # Matomo issue for automatic Matomo setup: https://github.com/matomo-org/matomo/issues/10257
-      # TODO: find a nice way to do this when more NixOS MySQL and / or Matomo automatic setup stuff is implemented.
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enable Matomo web analytics with php-fpm backend.
-          Either the nginx option or the webServerUser option is mandatory.
-        '';
-      };
-
-      package = mkPackageOption pkgs "matomo" { };
-
-      webServerUser = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "lighttpd";
-        description = ''
-          Name of the web server user that forwards requests to {option}`services.phpfpm.pools.<name>.socket` the fastcgi socket for Matomo if the nginx
-          option is not used. Either this option or the nginx option is mandatory.
-          If you want to use another webserver than nginx, you need to set this to that server's user
-          and pass fastcgi requests to `index.php`, `matomo.php` and `piwik.php` (legacy name) to this socket.
-        '';
-      };
-
-      periodicArchiveProcessing = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Enable periodic archive processing, which generates aggregated reports from the visits.
-
-          This means that you can safely disable browser triggers for Matomo archiving,
-          and safely enable to delete old visitor logs.
-          Before deleting visitor logs,
-          make sure though that you run `systemctl start matomo-archive-processing.service`
-          at least once without errors if you have already collected data before.
-        '';
-      };
-
-      hostname = mkOption {
-        type = types.str;
-        default = "${user}.${config.networking.fqdnOrHostName}";
-        defaultText = literalExpression ''
-          "${user}.''${config.${options.networking.fqdnOrHostName}}"
-        '';
-        example = "matomo.yourdomain.org";
-        description = ''
-          URL of the host, without https prefix. You may want to change it if you
-          run Matomo on a different URL than matomo.yourdomain.
-        '';
-      };
-
-      nginx = mkOption {
-        type = types.nullOr (types.submodule (
-          recursiveUpdate
-            (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
-            {
-              # enable encryption by default,
-              # as sensitive login and Matomo data should not be transmitted in clear text.
-              options.forceSSL.default = true;
-              options.enableACME.default = true;
-            }
-        )
-        );
-        default = null;
-        example = literalExpression ''
-          {
-            serverAliases = [
-              "matomo.''${config.networking.domain}"
-              "stats.''${config.networking.domain}"
-            ];
-            enableACME = false;
-          }
-        '';
-        description = ''
-            With this option, you can customize an nginx virtualHost which already has sensible defaults for Matomo.
-            Either this option or the webServerUser option is mandatory.
-            Set this to {} to just enable the virtualHost if you don't need any customization.
-            If enabled, then by default, the {option}`serverName` is
-            `''${user}.''${config.networking.hostName}.''${config.networking.domain}`,
-            SSL is active, and certificates are acquired via ACME.
-            If this is set to null (the default), no nginx virtualHost will be configured.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    warnings = mkIf (cfg.nginx != null && cfg.webServerUser != null) [
-      "If services.matomo.nginx is set, services.matomo.nginx.webServerUser is ignored and should be removed."
-    ];
-
-    assertions = [ {
-        assertion = cfg.nginx != null || cfg.webServerUser != null;
-        message = "Either services.matomo.nginx or services.matomo.nginx.webServerUser is mandatory";
-    }];
-
-    users.users.${user} = {
-      isSystemUser = true;
-      createHome = true;
-      home = dataDir;
-      group  = user;
-    };
-    users.groups.${user} = {};
-
-    systemd.services.matomo-setup-update = {
-      # everything needs to set up and up to date before Matomo php files are executed
-      requiredBy = [ "${phpExecutionUnit}.service" ];
-      before = [ "${phpExecutionUnit}.service" ];
-      # the update part of the script can only work if the database is already up and running
-      requires = [ databaseService ];
-      after = [ databaseService ];
-      path = [ cfg.package ];
-      environment.PIWIK_USER_PATH = dataDir;
-      serviceConfig = {
-        Type = "oneshot";
-        User = user;
-        # hide especially config.ini.php from other
-        UMask = "0007";
-        # TODO: might get renamed to MATOMO_USER_PATH in future versions
-        # chown + chmod in preStart needs root
-        PermissionsStartOnly = true;
-      };
-
-      # correct ownership and permissions in case they're not correct anymore,
-      # e.g. after restoring from backup or moving from another system.
-      # Note that ${dataDir}/config/config.ini.php might contain the MySQL password.
-      preStart = ''
-        # migrate data from piwik to Matomo folder
-        if [ -d ${deprecatedDataDir} ]; then
-          echo "Migrating from ${deprecatedDataDir} to ${dataDir}"
-          mv -T ${deprecatedDataDir} ${dataDir}
-        fi
-        chown -R ${user}:${user} ${dataDir}
-        chmod -R ug+rwX,o-rwx ${dataDir}
-
-        if [ -e ${dataDir}/current-package ]; then
-          CURRENT_PACKAGE=$(readlink ${dataDir}/current-package)
-          NEW_PACKAGE=${cfg.package}
-          if [ "$CURRENT_PACKAGE" != "$NEW_PACKAGE" ]; then
-            # keeping tmp around between upgrades seems to bork stuff, so delete it
-            rm -rf ${dataDir}/tmp
-          fi
-        elif [ -e ${dataDir}/tmp ]; then
-          # upgrade from 4.4.1
-          rm -rf ${dataDir}/tmp
-        fi
-        ln -sfT ${cfg.package} ${dataDir}/current-package
-        '';
-      script = ''
-            # Use User-Private Group scheme to protect Matomo data, but allow administration / backup via 'matomo' group
-            # Copy config folder
-            chmod g+s "${dataDir}"
-            cp -r "${cfg.package}/share/config" "${dataDir}/"
-            mkdir -p "${dataDir}/misc"
-            chmod -R u+rwX,g+rwX,o-rwx "${dataDir}"
-
-            # check whether user setup has already been done
-            if test -f "${dataDir}/config/config.ini.php"; then
-              # then execute possibly pending database upgrade
-              matomo-console core:update --yes
-            fi
-      '';
-    };
-
-    # If this is run regularly via the timer,
-    # 'Browser trigger archiving' can be disabled in Matomo UI > Settings > General Settings.
-    systemd.services.matomo-archive-processing = {
-      description = "Archive Matomo reports";
-      # the archiving can only work if the database is already up and running
-      requires = [ databaseService ];
-      after = [ databaseService ];
-
-      # TODO: might get renamed to MATOMO_USER_PATH in future versions
-      environment.PIWIK_USER_PATH = dataDir;
-      serviceConfig = {
-        Type = "oneshot";
-        User = user;
-        UMask = "0007";
-        CPUSchedulingPolicy = "idle";
-        IOSchedulingClass = "idle";
-        ExecStart = "${cfg.package}/bin/matomo-console core:archive --url=https://${cfg.hostname}";
-      };
-    };
-
-    systemd.timers.matomo-archive-processing = mkIf cfg.periodicArchiveProcessing {
-      description = "Automatically archive Matomo reports every hour";
-
-      wantedBy = [ "timers.target" ];
-      timerConfig = {
-        OnCalendar = "hourly";
-        Persistent = "yes";
-        AccuracySec = "10m";
-      };
-    };
-
-    systemd.services.${phpExecutionUnit} = {
-      # stop phpfpm on package upgrade, do database upgrade via matomo-setup-update, and then restart
-      restartTriggers = [ cfg.package ];
-      # stop config.ini.php from getting written with read permission for others
-      serviceConfig.UMask = "0007";
-    };
-
-    services.phpfpm.pools = let
-      # workaround for when both are null and need to generate a string,
-      # which is illegal, but as assertions apparently are being triggered *after* config generation,
-      # we have to avoid already throwing errors at this previous stage.
-      socketOwner = if (cfg.nginx != null) then config.services.nginx.user
-      else if (cfg.webServerUser != null) then cfg.webServerUser else "";
-    in {
-      ${pool} = {
-        inherit user;
-        phpOptions = ''
-          error_log = 'stderr'
-          log_errors = on
-        '';
-        settings = mapAttrs (name: mkDefault) {
-          "listen.owner" = socketOwner;
-          "listen.group" = "root";
-          "listen.mode" = "0660";
-          "pm" = "dynamic";
-          "pm.max_children" = 75;
-          "pm.start_servers" = 10;
-          "pm.min_spare_servers" = 5;
-          "pm.max_spare_servers" = 20;
-          "pm.max_requests" = 500;
-          "catch_workers_output" = true;
-        };
-        phpEnv.PIWIK_USER_PATH = dataDir;
-      };
-    };
-
-
-    services.nginx.virtualHosts = mkIf (cfg.nginx != null) {
-      # References:
-      # https://fralef.me/piwik-hardening-with-nginx-and-php-fpm.html
-      # https://github.com/perusio/piwik-nginx
-      "${cfg.hostname}" = mkMerge [ cfg.nginx {
-        # don't allow to override the root easily, as it will almost certainly break Matomo.
-        # disadvantage: not shown as default in docs.
-        root = mkForce "${cfg.package}/share";
-
-        # define locations here instead of as the submodule option's default
-        # so that they can easily be extended with additional locations if required
-        # without needing to redefine the Matomo ones.
-        # disadvantage: not shown as default in docs.
-        locations."/" = {
-          index = "index.php";
-        };
-        # allow index.php for webinterface
-        locations."= /index.php".extraConfig = ''
-          fastcgi_pass unix:${fpm.socket};
-        '';
-        # allow matomo.php for tracking
-        locations."= /matomo.php".extraConfig = ''
-          fastcgi_pass unix:${fpm.socket};
-        '';
-        # allow piwik.php for tracking (deprecated name)
-        locations."= /piwik.php".extraConfig = ''
-          fastcgi_pass unix:${fpm.socket};
-        '';
-        # Any other attempt to access any php files is forbidden
-        locations."~* ^.+\\.php$".extraConfig = ''
-          return 403;
-        '';
-        # Disallow access to unneeded directories
-        # config and tmp are already removed
-        locations."~ ^/(?:core|lang|misc)/".extraConfig = ''
-          return 403;
-        '';
-        # Disallow access to several helper files
-        locations."~* \\.(?:bat|git|ini|sh|txt|tpl|xml|md)$".extraConfig = ''
-          return 403;
-        '';
-        # No crawling of this site for bots that obey robots.txt - no useful information here.
-        locations."= /robots.txt".extraConfig = ''
-          return 200 "User-agent: *\nDisallow: /\n";
-        '';
-        # let browsers cache matomo.js
-        locations."= /matomo.js".extraConfig = ''
-          expires 1M;
-        '';
-        # let browsers cache piwik.js (deprecated name)
-        locations."= /piwik.js".extraConfig = ''
-          expires 1M;
-        '';
-      }];
-    };
-  };
-
-  meta = {
-    doc = ./matomo.md;
-    maintainers = with lib.maintainers; [ florianjacob ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix
deleted file mode 100644
index fee0ec2d641d..000000000000
--- a/nixos/modules/services/web-apps/mattermost.nix
+++ /dev/null
@@ -1,348 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.mattermost;
-
-  database = "postgres://${cfg.localDatabaseUser}:${cfg.localDatabasePassword}@localhost:5432/${cfg.localDatabaseName}?sslmode=disable&connect_timeout=10";
-
-  postgresPackage = config.services.postgresql.package;
-
-  createDb = {
-    statePath ? cfg.statePath,
-    localDatabaseUser ? cfg.localDatabaseUser,
-    localDatabasePassword ? cfg.localDatabasePassword,
-    localDatabaseName ? cfg.localDatabaseName,
-    useSudo ? true
-  }: ''
-    if ! test -e ${escapeShellArg "${statePath}/.db-created"}; then
-      ${lib.optionalString useSudo "${pkgs.sudo}/bin/sudo -u ${escapeShellArg config.services.postgresql.superUser} \\"}
-        ${postgresPackage}/bin/psql postgres -c \
-          "CREATE ROLE ${localDatabaseUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${localDatabasePassword}'"
-      ${lib.optionalString useSudo "${pkgs.sudo}/bin/sudo -u ${escapeShellArg config.services.postgresql.superUser} \\"}
-        ${postgresPackage}/bin/createdb \
-          --owner ${escapeShellArg localDatabaseUser} ${escapeShellArg localDatabaseName}
-      touch ${escapeShellArg "${statePath}/.db-created"}
-    fi
-  '';
-
-  mattermostPluginDerivations = with pkgs;
-    map (plugin: stdenv.mkDerivation {
-      name = "mattermost-plugin";
-      installPhase = ''
-        mkdir -p $out/share
-        cp ${plugin} $out/share/plugin.tar.gz
-      '';
-      dontUnpack = true;
-      dontPatch = true;
-      dontConfigure = true;
-      dontBuild = true;
-      preferLocalBuild = true;
-    }) cfg.plugins;
-
-  mattermostPlugins = with pkgs;
-    if mattermostPluginDerivations == [] then null
-    else stdenv.mkDerivation {
-      name = "${cfg.package.name}-plugins";
-      nativeBuildInputs = [
-        autoPatchelfHook
-      ] ++ mattermostPluginDerivations;
-      buildInputs = [
-        cfg.package
-      ];
-      installPhase = ''
-        mkdir -p $out/data/plugins
-        plugins=(${escapeShellArgs (map (plugin: "${plugin}/share/plugin.tar.gz") mattermostPluginDerivations)})
-        for plugin in "''${plugins[@]}"; do
-          hash="$(sha256sum "$plugin" | cut -d' ' -f1)"
-          mkdir -p "$hash"
-          tar -C "$hash" -xzf "$plugin"
-          autoPatchelf "$hash"
-          GZIP_OPT=-9 tar -C "$hash" -cvzf "$out/data/plugins/$hash.tar.gz" .
-          rm -rf "$hash"
-        done
-      '';
-
-      dontUnpack = true;
-      dontPatch = true;
-      dontConfigure = true;
-      dontBuild = true;
-      preferLocalBuild = true;
-    };
-
-  mattermostConfWithoutPlugins = recursiveUpdate
-    { ServiceSettings.SiteURL = cfg.siteUrl;
-      ServiceSettings.ListenAddress = cfg.listenAddress;
-      TeamSettings.SiteName = cfg.siteName;
-      SqlSettings.DriverName = "postgres";
-      SqlSettings.DataSource = database;
-      PluginSettings.Directory = "${cfg.statePath}/plugins/server";
-      PluginSettings.ClientDirectory = "${cfg.statePath}/plugins/client";
-    }
-    cfg.extraConfig;
-
-  mattermostConf = recursiveUpdate
-    mattermostConfWithoutPlugins
-    (
-      lib.optionalAttrs (mattermostPlugins != null) {
-        PluginSettings = {
-          Enable = true;
-        };
-      }
-    );
-
-  mattermostConfJSON = pkgs.writeText "mattermost-config.json" (builtins.toJSON mattermostConf);
-
-in
-
-{
-  options = {
-    services.mattermost = {
-      enable = mkEnableOption "Mattermost chat server";
-
-      package = mkPackageOption pkgs "mattermost" { };
-
-      statePath = mkOption {
-        type = types.str;
-        default = "/var/lib/mattermost";
-        description = "Mattermost working directory";
-      };
-
-      siteUrl = mkOption {
-        type = types.str;
-        example = "https://chat.example.com";
-        description = ''
-          URL this Mattermost instance is reachable under, without trailing slash.
-        '';
-      };
-
-      siteName = mkOption {
-        type = types.str;
-        default = "Mattermost";
-        description = "Name of this Mattermost site.";
-      };
-
-      listenAddress = mkOption {
-        type = types.str;
-        default = ":8065";
-        example = "[::1]:8065";
-        description = ''
-          Address and port this Mattermost instance listens to.
-        '';
-      };
-
-      mutableConfig = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether the Mattermost config.json is writeable by Mattermost.
-
-          Most of the settings can be edited in the system console of
-          Mattermost if this option is enabled. A template config using
-          the options specified in services.mattermost will be generated
-          but won't be overwritten on changes or rebuilds.
-
-          If this option is disabled, changes in the system console won't
-          be possible (default). If an config.json is present, it will be
-          overwritten!
-        '';
-      };
-
-      preferNixConfig = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          If both mutableConfig and this option are set, the Nix configuration
-          will take precedence over any settings configured in the server
-          console.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.attrs;
-        default = { };
-        description = ''
-          Additional configuration options as Nix attribute set in config.json schema.
-        '';
-      };
-
-      plugins = mkOption {
-        type = types.listOf (types.oneOf [types.path types.package]);
-        default = [];
-        example = "[ ./com.github.moussetc.mattermost.plugin.giphy-2.0.0.tar.gz ]";
-        description = ''
-          Plugins to add to the configuration. Overrides any installed if non-null.
-          This is a list of paths to .tar.gz files or derivations evaluating to
-          .tar.gz files.
-        '';
-      };
-      environmentFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = ''
-          Environment file (see {manpage}`systemd.exec(5)`
-          "EnvironmentFile=" section for the syntax) which sets config options
-          for mattermost (see [the mattermost documentation](https://docs.mattermost.com/configure/configuration-settings.html#environment-variables)).
-
-          Settings defined in the environment file will overwrite settings
-          set via nix or via the {option}`services.mattermost.extraConfig`
-          option.
-
-          Useful for setting config options without their value ending up in the
-          (world-readable) nix store, e.g. for a database password.
-        '';
-      };
-
-      localDatabaseCreate = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Create a local PostgreSQL database for Mattermost automatically.
-        '';
-      };
-
-      localDatabaseName = mkOption {
-        type = types.str;
-        default = "mattermost";
-        description = ''
-          Local Mattermost database name.
-        '';
-      };
-
-      localDatabaseUser = mkOption {
-        type = types.str;
-        default = "mattermost";
-        description = ''
-          Local Mattermost database username.
-        '';
-      };
-
-      localDatabasePassword = mkOption {
-        type = types.str;
-        default = "mmpgsecret";
-        description = ''
-          Password for local Mattermost database user.
-        '';
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "mattermost";
-        description = ''
-          User which runs the Mattermost service.
-        '';
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "mattermost";
-        description = ''
-          Group which runs the Mattermost service.
-        '';
-      };
-
-      matterircd = {
-        enable = mkEnableOption "Mattermost IRC bridge";
-        package = mkPackageOption pkgs "matterircd" { };
-        parameters = mkOption {
-          type = types.listOf types.str;
-          default = [ ];
-          example = [ "-mmserver chat.example.com" "-bind [::]:6667" ];
-          description = ''
-            Set commandline parameters to pass to matterircd. See
-            https://github.com/42wim/matterircd#usage for more information.
-          '';
-        };
-      };
-    };
-  };
-
-  config = mkMerge [
-    (mkIf cfg.enable {
-      users.users = optionalAttrs (cfg.user == "mattermost") {
-        mattermost = {
-          group = cfg.group;
-          uid = config.ids.uids.mattermost;
-          home = cfg.statePath;
-        };
-      };
-
-      users.groups = optionalAttrs (cfg.group == "mattermost") {
-        mattermost.gid = config.ids.gids.mattermost;
-      };
-
-      services.postgresql.enable = cfg.localDatabaseCreate;
-
-      # The systemd service will fail to execute the preStart hook
-      # if the WorkingDirectory does not exist
-      systemd.tmpfiles.settings."10-mattermost".${cfg.statePath}.d = { };
-
-      systemd.services.mattermost = {
-        description = "Mattermost chat service";
-        wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" "postgresql.service" ];
-
-        preStart = ''
-          mkdir -p "${cfg.statePath}"/{data,config,logs,plugins}
-          mkdir -p "${cfg.statePath}/plugins"/{client,server}
-          ln -sf ${cfg.package}/{bin,fonts,i18n,templates,client} "${cfg.statePath}"
-        '' + lib.optionalString (mattermostPlugins != null) ''
-          rm -rf "${cfg.statePath}/data/plugins"
-          ln -sf ${mattermostPlugins}/data/plugins "${cfg.statePath}/data"
-        '' + lib.optionalString (!cfg.mutableConfig) ''
-          rm -f "${cfg.statePath}/config/config.json"
-          ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.statePath}/config/config.json"
-        '' + lib.optionalString cfg.mutableConfig ''
-          if ! test -e "${cfg.statePath}/config/.initial-created"; then
-            rm -f ${cfg.statePath}/config/config.json
-            ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.statePath}/config/config.json"
-            touch "${cfg.statePath}/config/.initial-created"
-          fi
-        '' + lib.optionalString (cfg.mutableConfig && cfg.preferNixConfig) ''
-          new_config="$(${pkgs.jq}/bin/jq -s '.[0] * .[1]' "${cfg.statePath}/config/config.json" ${mattermostConfJSON})"
-
-          rm -f "${cfg.statePath}/config/config.json"
-          echo "$new_config" > "${cfg.statePath}/config/config.json"
-        '' + lib.optionalString cfg.localDatabaseCreate (createDb {}) + ''
-          # Don't change permissions recursively on the data, current, and symlinked directories (see ln -sf command above).
-          # This dramatically decreases startup times for installations with a lot of files.
-          find . -maxdepth 1 -not -name data -not -name client -not -name templates -not -name i18n -not -name fonts -not -name bin -not -name . \
-            -exec chown "${cfg.user}:${cfg.group}" -R {} \; -exec chmod u+rw,g+r,o-rwx -R {} \;
-
-          chown "${cfg.user}:${cfg.group}" "${cfg.statePath}/data" .
-          chmod u+rw,g+r,o-rwx "${cfg.statePath}/data" .
-        '';
-
-        serviceConfig = {
-          PermissionsStartOnly = true;
-          User = cfg.user;
-          Group = cfg.group;
-          ExecStart = "${cfg.package}/bin/mattermost";
-          WorkingDirectory = "${cfg.statePath}";
-          Restart = "always";
-          RestartSec = "10";
-          LimitNOFILE = "49152";
-          EnvironmentFile = cfg.environmentFile;
-        };
-        unitConfig.JoinsNamespaceOf = mkIf cfg.localDatabaseCreate "postgresql.service";
-      };
-    })
-    (mkIf cfg.matterircd.enable {
-      systemd.services.matterircd = {
-        description = "Mattermost IRC bridge service";
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          User = "nobody";
-          Group = "nogroup";
-          ExecStart = "${cfg.matterircd.package}/bin/matterircd ${escapeShellArgs cfg.matterircd.parameters}";
-          WorkingDirectory = "/tmp";
-          PrivateTmp = true;
-          Restart = "always";
-          RestartSec = "5";
-        };
-      };
-    })
-  ];
-}
diff --git a/nixos/modules/services/web-apps/mealie.nix b/nixos/modules/services/web-apps/mealie.nix
deleted file mode 100644
index 2484b2489c0d..000000000000
--- a/nixos/modules/services/web-apps/mealie.nix
+++ /dev/null
@@ -1,78 +0,0 @@
-{ config, lib, pkgs, ...}:
-let
-  cfg = config.services.mealie;
-  pkg = cfg.package;
-in
-{
-  options.services.mealie = {
-    enable = lib.mkEnableOption "Mealie, a recipe manager and meal planner";
-
-    package = lib.mkPackageOption pkgs "mealie" { };
-
-    listenAddress = lib.mkOption {
-      type = lib.types.str;
-      default = "0.0.0.0";
-      description = "Address on which the service should listen.";
-    };
-
-    port = lib.mkOption {
-      type = lib.types.port;
-      default = 9000;
-      description = "Port on which to serve the Mealie service.";
-    };
-
-    settings = lib.mkOption {
-      type = with lib.types; attrsOf anything;
-      default = {};
-      description = ''
-        Configuration of the Mealie service.
-
-        See [the mealie documentation](https://nightly.mealie.io/documentation/getting-started/installation/backend-config/) for available options and default values.
-      '';
-      example = {
-        ALLOW_SIGNUP = "false";
-      };
-    };
-
-    credentialsFile = lib.mkOption {
-      type = with lib.types; nullOr path;
-      default = null;
-      example = "/run/secrets/mealie-credentials.env";
-      description = ''
-        File containing credentials used in mealie such as {env}`POSTGRES_PASSWORD`
-        or sensitive LDAP options.
-
-        Expects the format of an `EnvironmentFile=`, as described by {manpage}`systemd.exec(5)`.
-      '';
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.mealie = {
-      description = "Mealie, a self hosted recipe manager and meal planner";
-
-      after = [ "network-online.target" ];
-      wants = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      environment = {
-        PRODUCTION = "true";
-        ALEMBIC_CONFIG_FILE="${pkg}/config/alembic.ini";
-        API_PORT = toString cfg.port;
-        BASE_URL = "http://localhost:${toString cfg.port}";
-        DATA_DIR = "/var/lib/mealie";
-        CRF_MODEL_PATH = "/var/lib/mealie/model.crfmodel";
-      } // (builtins.mapAttrs (_: val: toString val) cfg.settings);
-
-      serviceConfig = {
-        DynamicUser = true;
-        User = "mealie";
-        ExecStartPre = "${pkg}/libexec/init_db";
-        ExecStart = "${lib.getExe pkg} -b ${cfg.listenAddress}:${builtins.toString cfg.port}";
-        EnvironmentFile = lib.mkIf (cfg.credentialsFile != null) cfg.credentialsFile;
-        StateDirectory = "mealie";
-        StandardOutput="journal";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
deleted file mode 100644
index b11626ec2dc3..000000000000
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ /dev/null
@@ -1,642 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-
-  inherit (lib) mkDefault mkEnableOption mkPackageOption mkForce mkIf mkMerge mkOption;
-  inherit (lib) concatStringsSep literalExpression mapAttrsToList optional optionals optionalString types;
-
-  cfg = config.services.mediawiki;
-  fpm = config.services.phpfpm.pools.mediawiki;
-  user = "mediawiki";
-  group =
-    if cfg.webserver == "apache" then
-      config.services.httpd.group
-    else if cfg.webserver == "nginx" then
-      config.services.nginx.group
-    else "mediawiki";
-
-  cacheDir = "/var/cache/mediawiki";
-  stateDir = "/var/lib/mediawiki";
-
-  # https://www.mediawiki.org/wiki/Compatibility
-  php = pkgs.php81;
-
-  pkg = pkgs.stdenv.mkDerivation rec {
-    pname = "mediawiki-full";
-    inherit (src) version;
-    src = cfg.package;
-
-    installPhase = ''
-      mkdir -p $out
-      cp -r * $out/
-
-      # try removing directories before symlinking to allow overwriting any builtin extension or skin
-      ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
-        rm -rf $out/share/mediawiki/skins/${k}
-        ln -s ${v} $out/share/mediawiki/skins/${k}
-      '') cfg.skins)}
-
-      ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
-        rm -rf $out/share/mediawiki/extensions/${k}
-        ln -s ${if v != null then v else "$src/share/mediawiki/extensions/${k}"} $out/share/mediawiki/extensions/${k}
-      '') cfg.extensions)}
-    '';
-  };
-
-  mediawikiScripts = pkgs.runCommand "mediawiki-scripts" {
-    nativeBuildInputs = [ pkgs.makeWrapper ];
-    preferLocalBuild = true;
-  } ''
-    mkdir -p $out/bin
-    for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do
-      makeWrapper ${php}/bin/php $out/bin/mediawiki-$(basename $i .php) \
-        --set MEDIAWIKI_CONFIG ${mediawikiConfig} \
-        --add-flags ${pkg}/share/mediawiki/maintenance/$i
-    done
-  '';
-
-  dbAddr = if cfg.database.socket == null then
-    "${cfg.database.host}:${toString cfg.database.port}"
-  else if cfg.database.type == "mysql" then
-    "${cfg.database.host}:${cfg.database.socket}"
-  else if cfg.database.type == "postgres" then
-    "${cfg.database.socket}"
-  else
-    throw "Unsupported database type: ${cfg.database.type} for socket: ${cfg.database.socket}";
-
-  mediawikiConfig = pkgs.writeText "LocalSettings.php" ''
-    <?php
-      # Protect against web entry
-      if ( !defined( 'MEDIAWIKI' ) ) {
-        exit;
-      }
-
-      $wgSitename = "${cfg.name}";
-      $wgMetaNamespace = false;
-
-      ## The URL base path to the directory containing the wiki;
-      ## defaults for all runtime URL paths are based off of this.
-      ## For more information on customizing the URLs
-      ## (like /w/index.php/Page_title to /wiki/Page_title) please see:
-      ## https://www.mediawiki.org/wiki/Manual:Short_URL
-      $wgScriptPath = "${lib.optionalString (cfg.webserver == "nginx") "/w"}";
-
-      ## The protocol and server name to use in fully-qualified URLs
-      $wgServer = "${cfg.url}";
-
-      ## The URL path to static resources (images, scripts, etc.)
-      $wgResourceBasePath = $wgScriptPath;
-
-      ${lib.optionalString (cfg.webserver == "nginx") ''
-        $wgArticlePath = "/wiki/$1";
-        $wgUsePathInfo = true;
-      ''}
-
-      ## The URL path to the logo.  Make sure you change this from the default,
-      ## or else you'll overwrite your logo when you upgrade!
-      $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
-
-      ## UPO means: this is also a user preference option
-
-      $wgEnableEmail = true;
-      $wgEnableUserEmail = true; # UPO
-
-      $wgPasswordSender = "${cfg.passwordSender}";
-
-      $wgEnotifUserTalk = false; # UPO
-      $wgEnotifWatchlist = false; # UPO
-      $wgEmailAuthentication = true;
-
-      ## Database settings
-      $wgDBtype = "${cfg.database.type}";
-      $wgDBserver = "${dbAddr}";
-      $wgDBport = "${toString cfg.database.port}";
-      $wgDBname = "${cfg.database.name}";
-      $wgDBuser = "${cfg.database.user}";
-      ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"}
-
-      ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) ''
-        # MySQL specific settings
-        $wgDBprefix = "${cfg.database.tablePrefix}";
-      ''}
-
-      ${optionalString (cfg.database.type == "mysql") ''
-        # MySQL table options to use during installation or update
-        $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary";
-      ''}
-
-      ## Shared memory settings
-      $wgMainCacheType = CACHE_NONE;
-      $wgMemCachedServers = [];
-
-      ${optionalString (cfg.uploadsDir != null) ''
-        $wgEnableUploads = true;
-        $wgUploadDirectory = "${cfg.uploadsDir}";
-      ''}
-
-      $wgUseImageMagick = true;
-      $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert";
-
-      # InstantCommons allows wiki to use images from https://commons.wikimedia.org
-      $wgUseInstantCommons = false;
-
-      # Periodically send a pingback to https://www.mediawiki.org/ with basic data
-      # about this MediaWiki instance. The Wikimedia Foundation shares this data
-      # with MediaWiki developers to help guide future development efforts.
-      $wgPingback = true;
-
-      ## If you use ImageMagick (or any other shell command) on a
-      ## Linux server, this will need to be set to the name of an
-      ## available UTF-8 locale
-      $wgShellLocale = "C.UTF-8";
-
-      ## Set $wgCacheDirectory to a writable directory on the web server
-      ## to make your wiki go slightly faster. The directory should not
-      ## be publicly accessible from the web.
-      $wgCacheDirectory = "${cacheDir}";
-
-      # Site language code, should be one of the list in ./languages/data/Names.php
-      $wgLanguageCode = "en";
-
-      $wgSecretKey = file_get_contents("${stateDir}/secret.key");
-
-      # Changing this will log out all existing sessions.
-      $wgAuthenticationTokenVersion = "";
-
-      ## For attaching licensing metadata to pages, and displaying an
-      ## appropriate copyright notice / icon. GNU Free Documentation
-      ## License and Creative Commons licenses are supported so far.
-      $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
-      $wgRightsUrl = "";
-      $wgRightsText = "";
-      $wgRightsIcon = "";
-
-      # Path to the GNU diff3 utility. Used for conflict resolution.
-      $wgDiff = "${pkgs.diffutils}/bin/diff";
-      $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
-
-      # Enabled skins.
-      ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)}
-
-      # Enabled extensions.
-      ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)}
-
-
-      # End of automatically generated settings.
-      # Add more configuration options below.
-
-      ${cfg.extraConfig}
-  '';
-
-  withTrailingSlash = str: if lib.hasSuffix "/" str then str else "${str}/";
-in
-{
-  # interface
-  options = {
-    services.mediawiki = {
-
-      enable = mkEnableOption "MediaWiki";
-
-      package = mkPackageOption pkgs "mediawiki" { };
-
-      finalPackage = mkOption {
-        type = types.package;
-        readOnly = true;
-        default = pkg;
-        defaultText = literalExpression "pkg";
-        description = ''
-          The final package used by the module. This is the package that will have extensions and skins installed.
-        '';
-      };
-
-      name = mkOption {
-        type = types.str;
-        default = "MediaWiki";
-        example = "Foobar Wiki";
-        description = "Name of the wiki.";
-      };
-
-      url = mkOption {
-        type = types.str;
-        default =
-          if cfg.webserver == "apache" then
-            "${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://${cfg.httpd.virtualHost.hostName}"
-          else if cfg.webserver == "nginx" then
-            let
-              hasSSL = host: host.forceSSL || host.addSSL;
-            in
-            "${if hasSSL config.services.nginx.virtualHosts.${cfg.nginx.hostName} then "https" else "http"}://${cfg.nginx.hostName}"
-          else
-            "http://localhost";
-        defaultText = ''
-          if "mediawiki uses ssl" then "{"https" else "http"}://''${cfg.hostName}" else "http://localhost";
-        '';
-        example = "https://wiki.example.org";
-        description = "URL of the wiki.";
-      };
-
-      uploadsDir = mkOption {
-        type = types.nullOr types.path;
-        default = "${stateDir}/uploads";
-        description = ''
-          This directory is used for uploads of pictures. The directory passed here is automatically
-          created and permissions adjusted as required.
-        '';
-      };
-
-      passwordFile = mkOption {
-        type = types.path;
-        description = ''
-          A file containing the initial password for the administrator account "admin".
-        '';
-        example = "/run/keys/mediawiki-password";
-      };
-
-      passwordSender = mkOption {
-        type = types.str;
-        default =
-          if cfg.webserver == "apache" then
-            if cfg.httpd.virtualHost.adminAddr != null then
-              cfg.httpd.virtualHost.adminAddr
-            else
-              config.services.httpd.adminAddr else "root@localhost";
-        defaultText = literalExpression ''
-          if cfg.webserver == "apache" then
-            if cfg.httpd.virtualHost.adminAddr != null then
-              cfg.httpd.virtualHost.adminAddr
-            else
-              config.services.httpd.adminAddr else "root@localhost"
-        '';
-        description = "Contact address for password reset.";
-      };
-
-      skins = mkOption {
-        default = {};
-        type = types.attrsOf types.path;
-        description = ''
-          Attribute set of paths whose content is copied to the {file}`skins`
-          subdirectory of the MediaWiki installation in addition to the default skins.
-        '';
-      };
-
-      extensions = mkOption {
-        default = {};
-        type = types.attrsOf (types.nullOr types.path);
-        description = ''
-          Attribute set of paths whose content is copied to the {file}`extensions`
-          subdirectory of the MediaWiki installation and enabled in configuration.
-
-          Use `null` instead of path to enable extensions that are part of MediaWiki.
-        '';
-        example = literalExpression ''
-          {
-            Matomo = pkgs.fetchzip {
-              url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz";
-              sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b";
-            };
-            ParserFunctions = null;
-          }
-        '';
-      };
-
-      webserver = mkOption {
-        type = types.enum [ "apache" "none" "nginx" ];
-        default = "apache";
-        description = "Webserver to use.";
-      };
-
-      database = {
-        type = mkOption {
-          type = types.enum [ "mysql" "postgres" "mssql" "oracle" ];
-          default = "mysql";
-          description = "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers.";
-        };
-
-        host = mkOption {
-          type = types.str;
-          default = "localhost";
-          description = "Database host address.";
-        };
-
-        port = mkOption {
-          type = types.port;
-          default = if cfg.database.type == "mysql" then 3306 else 5432;
-          defaultText = literalExpression "3306";
-          description = "Database host port.";
-        };
-
-        name = mkOption {
-          type = types.str;
-          default = "mediawiki";
-          description = "Database name.";
-        };
-
-        user = mkOption {
-          type = types.str;
-          default = "mediawiki";
-          description = "Database user.";
-        };
-
-        passwordFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          example = "/run/keys/mediawiki-dbpassword";
-          description = ''
-            A file containing the password corresponding to
-            {option}`database.user`.
-          '';
-        };
-
-        tablePrefix = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          description = ''
-            If you only have access to a single database and wish to install more than
-            one version of MediaWiki, or have other applications that also use the
-            database, you can give the table names a unique prefix to stop any naming
-            conflicts or confusion.
-            See <https://www.mediawiki.org/wiki/Manual:$wgDBprefix>.
-          '';
-        };
-
-        socket = mkOption {
-          type = types.nullOr types.path;
-          default = if (cfg.database.type == "mysql" && cfg.database.createLocally) then
-              "/run/mysqld/mysqld.sock"
-            else if (cfg.database.type == "postgres" && cfg.database.createLocally) then
-              "/run/postgresql"
-            else
-              null;
-          defaultText = literalExpression "/run/mysqld/mysqld.sock";
-          description = "Path to the unix socket file to use for authentication.";
-        };
-
-        createLocally = mkOption {
-          type = types.bool;
-          default = cfg.database.type == "mysql" || cfg.database.type == "postgres";
-          defaultText = literalExpression "true";
-          description = ''
-            Create the database and database user locally.
-            This currently only applies if database type "mysql" is selected.
-          '';
-        };
-      };
-
-      nginx.hostName = mkOption {
-        type = types.str;
-        example = literalExpression ''wiki.example.com'';
-        default = "localhost";
-        description = ''
-          The hostname to use for the nginx virtual host.
-          This is used to generate the nginx configuration.
-        '';
-      };
-
-      httpd.virtualHost = mkOption {
-        type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-        example = literalExpression ''
-          {
-            hostName = "mediawiki.example.org";
-            adminAddr = "webmaster@example.org";
-            forceSSL = true;
-            enableACME = true;
-          }
-        '';
-        description = ''
-          Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`.
-          See [](#opt-services.httpd.virtualHosts) for further information.
-        '';
-      };
-
-      poolConfig = mkOption {
-        type = with types; attrsOf (oneOf [ str int bool ]);
-        default = {
-          "pm" = "dynamic";
-          "pm.max_children" = 32;
-          "pm.start_servers" = 2;
-          "pm.min_spare_servers" = 2;
-          "pm.max_spare_servers" = 4;
-          "pm.max_requests" = 500;
-        };
-        description = ''
-          Options for the MediaWiki PHP pool. See the documentation on `php-fpm.conf`
-          for details on configuration directives.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        description = ''
-          Any additional text to be appended to MediaWiki's
-          LocalSettings.php configuration file. For configuration
-          settings, see <https://www.mediawiki.org/wiki/Manual:Configuration_settings>.
-        '';
-        default = "";
-        example = ''
-          $wgEnableEmail = false;
-        '';
-      };
-
-    };
-  };
-
-  imports = [
-    (lib.mkRenamedOptionModule [ "services" "mediawiki" "virtualHost" ] [ "services" "mediawiki" "httpd" "virtualHost" ])
-  ];
-
-  # implementation
-  config = mkIf cfg.enable {
-
-    assertions = [
-      { assertion = cfg.database.createLocally -> (cfg.database.type == "mysql" || cfg.database.type == "postgres");
-        message = "services.mediawiki.createLocally is currently only supported for database type 'mysql' and 'postgres'";
-      }
-      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
-        message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true";
-      }
-      { assertion = cfg.database.createLocally -> cfg.database.socket != null;
-        message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true";
-      }
-      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
-        message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true";
-      }
-    ];
-
-    services.mediawiki.skins = {
-      MonoBook = "${cfg.package}/share/mediawiki/skins/MonoBook";
-      Timeless = "${cfg.package}/share/mediawiki/skins/Timeless";
-      Vector = "${cfg.package}/share/mediawiki/skins/Vector";
-    };
-
-    services.mysql = mkIf (cfg.database.type == "mysql" && cfg.database.createLocally) {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [{
-        name = cfg.database.user;
-        ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
-      }];
-    };
-
-    services.postgresql = mkIf (cfg.database.type == "postgres" && cfg.database.createLocally) {
-      enable = true;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [{
-        name = cfg.database.user;
-        ensureDBOwnership = true;
-      }];
-    };
-
-    services.phpfpm.pools.mediawiki = {
-      inherit user group;
-      phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}";
-      phpPackage = php;
-      settings = (if (cfg.webserver == "apache") then {
-        "listen.owner" = config.services.httpd.user;
-        "listen.group" = config.services.httpd.group;
-      } else if (cfg.webserver == "nginx") then {
-        "listen.owner" = config.services.nginx.user;
-        "listen.group" = config.services.nginx.group;
-      } else {
-        "listen.owner" = user;
-        "listen.group" = group;
-      }) // cfg.poolConfig;
-    };
-
-    services.httpd = lib.mkIf (cfg.webserver == "apache") {
-      enable = true;
-      extraModules = [ "proxy_fcgi" ];
-      virtualHosts.${cfg.httpd.virtualHost.hostName} = mkMerge [
-        cfg.httpd.virtualHost
-        {
-          documentRoot = mkForce "${pkg}/share/mediawiki";
-          extraConfig = ''
-            <Directory "${pkg}/share/mediawiki">
-              <FilesMatch "\.php$">
-                <If "-f %{REQUEST_FILENAME}">
-                  SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
-                </If>
-              </FilesMatch>
-
-              Require all granted
-              DirectoryIndex index.php
-              AllowOverride All
-            </Directory>
-          '' + optionalString (cfg.uploadsDir != null) ''
-            Alias "/images" "${cfg.uploadsDir}"
-            <Directory "${cfg.uploadsDir}">
-              Require all granted
-            </Directory>
-          '';
-        }
-      ];
-    };
-    # inspired by https://www.mediawiki.org/wiki/Manual:Short_URL/Nginx
-    services.nginx = lib.mkIf (cfg.webserver == "nginx") {
-      enable = true;
-      virtualHosts.${config.services.mediawiki.nginx.hostName} = {
-        root = "${pkg}/share/mediawiki";
-        locations = {
-          "~ ^/w/(index|load|api|thumb|opensearch_desc|rest|img_auth)\\.php$".extraConfig = ''
-            rewrite ^/w/(.*) /$1 break;
-            include ${config.services.nginx.package}/conf/fastcgi.conf;
-            fastcgi_index index.php;
-            fastcgi_pass unix:${config.services.phpfpm.pools.mediawiki.socket};
-          '';
-          "/w/images/".alias = withTrailingSlash cfg.uploadsDir;
-          # Deny access to deleted images folder
-          "/w/images/deleted".extraConfig = ''
-            deny all;
-          '';
-          # MediaWiki assets (usually images)
-          "~ ^/w/resources/(assets|lib|src)".extraConfig = ''
-            rewrite ^/w(/.*) $1 break;
-            add_header Cache-Control "public";
-            expires 7d;
-          '';
-          # Assets, scripts and styles from skins and extensions
-          "~ ^/w/(skins|extensions)/.+\\.(css|js|gif|jpg|jpeg|png|svg|wasm|ttf|woff|woff2)$".extraConfig = ''
-            rewrite ^/w(/.*) $1 break;
-            add_header Cache-Control "public";
-            expires 7d;
-          '';
-
-          # Handling for Mediawiki REST API, see [[mw:API:REST_API]]
-          "/w/rest.php/".tryFiles = "$uri $uri/ /w/rest.php?$query_string";
-
-          # Handling for the article path (pretty URLs)
-          "/wiki/".extraConfig = ''
-            rewrite ^/wiki/(?<pagename>.*)$ /w/index.php;
-          '';
-
-          # Explicit access to the root website, redirect to main page (adapt as needed)
-          "= /".extraConfig = ''
-            return 301 /wiki/;
-          '';
-
-          # Every other entry point will be disallowed.
-          # Add specific rules for other entry points/images as needed above this
-          "/".extraConfig = ''
-             return 404;
-          '';
-        };
-      };
-    };
-
-    systemd.tmpfiles.rules = [
-      "d '${stateDir}' 0750 ${user} ${group} - -"
-      "d '${cacheDir}' 0750 ${user} ${group} - -"
-    ] ++ optionals (cfg.uploadsDir != null) [
-      "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
-      "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
-    ];
-
-    systemd.services.mediawiki-init = {
-      wantedBy = [ "multi-user.target" ];
-      before = [ "phpfpm-mediawiki.service" ];
-      after = optional (cfg.database.type == "mysql" && cfg.database.createLocally) "mysql.service"
-              ++ optional (cfg.database.type == "postgres" && cfg.database.createLocally) "postgresql.service";
-      script = ''
-        if ! test -e "${stateDir}/secret.key"; then
-          tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 64 > ${stateDir}/secret.key
-        fi
-
-        echo "exit( wfGetDB( DB_MASTER )->tableExists( 'user' ) ? 1 : 0 );" | \
-        ${php}/bin/php ${pkg}/share/mediawiki/maintenance/eval.php --conf ${mediawikiConfig} && \
-        ${php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \
-          --confpath /tmp \
-          --scriptpath / \
-          --dbserver ${lib.escapeShellArg dbAddr} \
-          --dbport ${toString cfg.database.port} \
-          --dbname ${lib.escapeShellArg cfg.database.name} \
-          ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${lib.escapeShellArg cfg.database.tablePrefix}"} \
-          --dbuser ${lib.escapeShellArg cfg.database.user} \
-          ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${lib.escapeShellArg cfg.database.passwordFile}"} \
-          --passfile ${lib.escapeShellArg cfg.passwordFile} \
-          --dbtype ${cfg.database.type} \
-          ${lib.escapeShellArg cfg.name} \
-          admin
-
-        ${php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick
-      '';
-
-      serviceConfig = {
-        Type = "oneshot";
-        User = user;
-        Group = group;
-        PrivateTmp = true;
-      };
-    };
-
-    systemd.services.httpd.after = optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service"
-      ++ optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "postgres") "postgresql.service";
-
-    users.users.${user} = {
-      inherit group;
-      isSystemUser = true;
-    };
-    users.groups.${group} = {};
-
-    environment.systemPackages = [ mediawikiScripts ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/meme-bingo-web.nix b/nixos/modules/services/web-apps/meme-bingo-web.nix
deleted file mode 100644
index ab123ba8ef9c..000000000000
--- a/nixos/modules/services/web-apps/meme-bingo-web.nix
+++ /dev/null
@@ -1,88 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  inherit (lib) mkEnableOption mkPackageOption mkIf mkOption types literalExpression;
-
-  cfg = config.services.meme-bingo-web;
-in {
-  options = {
-    services.meme-bingo-web = {
-      enable = mkEnableOption ''
-        a web app for the meme bingo, rendered entirely on the web server and made interactive with forms.
-
-        Note: The application's author suppose to run meme-bingo-web behind a reverse proxy for SSL and HTTP/3
-      '';
-
-      package = mkPackageOption pkgs "meme-bingo-web" { };
-
-      baseUrl = mkOption {
-        description = ''
-          URL to be used for the HTML <base> element on all HTML routes.
-        '';
-        type = types.str;
-        default = "http://localhost:41678/";
-        example = "https://bingo.example.com/";
-      };
-      port = mkOption {
-        description = ''
-          Port to be used for the web server.
-        '';
-        type = types.port;
-        default = 41678;
-        example = 21035;
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.meme-bingo-web = {
-      description = "A web app for playing meme bingos.";
-      wantedBy = [ "multi-user.target" ];
-
-      environment = {
-        MEME_BINGO_BASE = cfg.baseUrl;
-        MEME_BINGO_PORT = toString cfg.port;
-      };
-      path = [ cfg.package ];
-
-      serviceConfig = {
-        User = "meme-bingo-web";
-        Group = "meme-bingo-web";
-
-        DynamicUser = true;
-
-        ExecStart = "${cfg.package}/bin/meme-bingo-web";
-
-        Restart = "always";
-        RestartSec = 1;
-
-        # Hardening
-        CapabilityBoundingSet = [ "" ];
-        DeviceAllow = [ "/dev/random" ];
-        LockPersonality = true;
-        PrivateDevices = true;
-        PrivateUsers = true;
-        ProcSubset = "pid";
-        ProtectSystem = "strict";
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
-        UMask = "0077";
-        RestrictSUIDSGID = true;
-        RemoveIPC = true;
-        NoNewPrivileges = true;
-        MemoryDenyWriteExecute = true;
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/microbin.nix b/nixos/modules/services/web-apps/microbin.nix
deleted file mode 100644
index 0ebe644a2595..000000000000
--- a/nixos/modules/services/web-apps/microbin.nix
+++ /dev/null
@@ -1,93 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.microbin;
-in
-{
-  options.services.microbin = {
-    enable = lib.mkEnableOption "MicroBin is a super tiny, feature rich, configurable paste bin web application";
-
-    package = lib.mkPackageOption pkgs "microbin" { };
-
-    settings = lib.mkOption {
-      type = lib.types.submodule { freeformType = with lib.types; attrsOf (oneOf [ bool int str ]); };
-      default = { };
-      example = {
-        MICROBIN_PORT = 8080;
-        MICROBIN_HIDE_LOGO = false;
-      };
-      description = ''
-        Additional configuration for MicroBin, see
-        <https://microbin.eu/docs/installation-and-configuration/configuration/>
-        for supported values.
-
-        For secrets use passwordFile option instead.
-      '';
-    };
-
-    dataDir = lib.mkOption {
-      type = lib.types.str;
-      default = "/var/lib/microbin";
-      description = "Default data folder for MicroBin.";
-    };
-
-    passwordFile = lib.mkOption {
-      type = lib.types.nullOr lib.types.path;
-      default = null;
-      example = "/run/secrets/microbin.env";
-      description = ''
-        Path to file containing environment variables.
-        Useful for passing down secrets.
-        Variables that can be considered secrets are:
-         - MICROBIN_BASIC_AUTH_USERNAME
-         - MICROBIN_BASIC_AUTH_PASSWORD
-         - MICROBIN_ADMIN_USERNAME
-         - MICROBIN_ADMIN_PASSWORD
-         - MICROBIN_UPLOADER_PASSWORD
-      '';
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    services.microbin.settings = with lib; {
-      MICROBIN_BIND = mkDefault "0.0.0.0";
-      MICROBIN_DISABLE_TELEMETRY = mkDefault true;
-      MICROBIN_LIST_SERVER = mkDefault false;
-      MICROBIN_PORT = mkDefault "8080";
-    };
-
-    systemd.services.microbin = {
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      environment = lib.mapAttrs (_: v: if lib.isBool v then lib.boolToString v else toString v) cfg.settings;
-      serviceConfig = {
-        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
-        DevicePolicy = "closed";
-        DynamicUser = true;
-        EnvironmentFile = lib.optional (cfg.passwordFile != null) cfg.passwordFile;
-        ExecStart = "${cfg.package}/bin/microbin";
-        LockPersonality = true;
-        MemoryDenyWriteExecute = true;
-        PrivateDevices = true;
-        PrivateUsers = true;
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        ReadWritePaths = cfg.dataDir;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        StateDirectory = "microbin";
-        SystemCallArchitectures = [ "native" ];
-        SystemCallFilter = [ "@system-service" ];
-        WorkingDirectory = cfg.dataDir;
-      };
-    };
-  };
-
-  meta.maintainers = with lib.maintainers; [ surfaceflinger ];
-}
diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
deleted file mode 100644
index 61243a63c582..000000000000
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ /dev/null
@@ -1,154 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  inherit (lib) mkEnableOption mkPackageOption mkOption types literalExpression mkIf mkDefault;
-  cfg = config.services.miniflux;
-
-  defaultAddress = "localhost:8080";
-
-  pgbin = "${config.services.postgresql.package}/bin";
-  preStart = pkgs.writeScript "miniflux-pre-start" ''
-    #!${pkgs.runtimeShell}
-    ${pgbin}/psql "miniflux" -c "CREATE EXTENSION IF NOT EXISTS hstore"
-  '';
-in
-
-{
-  options = {
-    services.miniflux = {
-      enable = mkEnableOption "miniflux";
-
-      package = mkPackageOption pkgs "miniflux" { };
-
-      createDatabaseLocally = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether a PostgreSQL database should be automatically created and
-          configured on the local host. If set to `false`, you need provision a
-          database yourself and make sure to create the hstore extension in it.
-        '';
-      };
-
-      config = mkOption {
-        type = with types; attrsOf (oneOf [ str int ]);
-        example = literalExpression ''
-          {
-            CLEANUP_FREQUENCY = 48;
-            LISTEN_ADDR = "localhost:8080";
-          }
-        '';
-        description = ''
-          Configuration for Miniflux, refer to
-          <https://miniflux.app/docs/configuration.html>
-          for documentation on the supported values.
-
-          Correct configuration for the database is already provided.
-          By default, listens on ${defaultAddress}.
-        '';
-      };
-
-      adminCredentialsFile = mkOption {
-        type = types.path;
-        description = ''
-          File containing the ADMIN_USERNAME and
-          ADMIN_PASSWORD (length >= 6) in the format of
-          an EnvironmentFile=, as described by systemd.exec(5).
-        '';
-        example = "/etc/nixos/miniflux-admin-credentials";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.miniflux.config = {
-      LISTEN_ADDR = mkDefault defaultAddress;
-      DATABASE_URL = lib.mkIf cfg.createDatabaseLocally "user=miniflux host=/run/postgresql dbname=miniflux";
-      RUN_MIGRATIONS = 1;
-      CREATE_ADMIN = 1;
-      WATCHDOG = 1;
-    };
-
-    services.postgresql = lib.mkIf cfg.createDatabaseLocally {
-      enable = true;
-      ensureUsers = [ {
-        name = "miniflux";
-        ensureDBOwnership = true;
-      } ];
-      ensureDatabases = [ "miniflux" ];
-    };
-
-    systemd.services.miniflux-dbsetup = lib.mkIf cfg.createDatabaseLocally {
-      description = "Miniflux database setup";
-      requires = [ "postgresql.service" ];
-      after = [ "network.target" "postgresql.service" ];
-      serviceConfig = {
-        Type = "oneshot";
-        User = config.services.postgresql.superUser;
-        ExecStart = preStart;
-      };
-    };
-
-    systemd.services.miniflux = {
-      description = "Miniflux service";
-      wantedBy = [ "multi-user.target" ];
-      requires = lib.optional cfg.createDatabaseLocally "miniflux-dbsetup.service";
-      after = [ "network.target" ]
-        ++ lib.optionals cfg.createDatabaseLocally [ "postgresql.service" "miniflux-dbsetup.service" ];
-
-      serviceConfig = {
-        Type = "notify";
-        ExecStart = lib.getExe cfg.package;
-        User = "miniflux";
-        DynamicUser = true;
-        RuntimeDirectory = "miniflux";
-        RuntimeDirectoryMode = "0750";
-        EnvironmentFile = cfg.adminCredentialsFile;
-        WatchdogSec = 60;
-        WatchdogSignal = "SIGKILL";
-        Restart = "always";
-        RestartSec = 5;
-
-        # Hardening
-        CapabilityBoundingSet = [ "" ];
-        DeviceAllow = [ "" ];
-        LockPersonality = true;
-        MemoryDenyWriteExecute = true;
-        PrivateDevices = true;
-        PrivateUsers = true;
-        ProcSubset = "pid";
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged" ];
-        UMask = "0077";
-      };
-
-      environment = lib.mapAttrs (_: toString) cfg.config;
-    };
-    environment.systemPackages = [ cfg.package ];
-
-    security.apparmor.policies."bin.miniflux".profile = ''
-      include <tunables/global>
-      ${cfg.package}/bin/miniflux {
-        include <abstractions/base>
-        include <abstractions/nameservice>
-        include <abstractions/ssl_certs>
-        include "${pkgs.apparmorRulesFromClosure { name = "miniflux"; } cfg.package}"
-        r ${cfg.package}/bin/miniflux,
-        r @{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size,
-        rw /run/miniflux/**,
-      }
-    '';
-  };
-}
diff --git a/nixos/modules/services/web-apps/mobilizon.nix b/nixos/modules/services/web-apps/mobilizon.nix
deleted file mode 100644
index b7fad7f3066e..000000000000
--- a/nixos/modules/services/web-apps/mobilizon.nix
+++ /dev/null
@@ -1,448 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-with lib;
-
-let
-  cfg = config.services.mobilizon;
-
-  user = "mobilizon";
-  group = "mobilizon";
-
-  settingsFormat = pkgs.formats.elixirConf { elixir = cfg.package.elixirPackage; };
-
-  configFile = settingsFormat.generate "mobilizon-config.exs" cfg.settings;
-
-  # Make a package containing launchers with the correct envirenment, instead of
-  # setting it with systemd services, so that the user can also use them without
-  # troubles
-  launchers = pkgs.stdenv.mkDerivation rec {
-    pname = "${cfg.package.pname}-launchers";
-    inherit (cfg.package) version;
-
-    src = cfg.package;
-
-    nativeBuildInputs = with pkgs; [ makeWrapper ];
-
-    dontBuild = true;
-
-    installPhase = ''
-      mkdir -p $out/bin
-
-      makeWrapper \
-        $src/bin/mobilizon \
-        $out/bin/mobilizon \
-        --run '. ${secretEnvFile}' \
-        --set MOBILIZON_CONFIG_PATH "${configFile}" \
-        --set-default RELEASE_TMP "/tmp"
-
-      makeWrapper \
-        $src/bin/mobilizon_ctl \
-        $out/bin/mobilizon_ctl \
-        --run '. ${secretEnvFile}' \
-        --set MOBILIZON_CONFIG_PATH "${configFile}" \
-        --set-default RELEASE_TMP "/tmp"
-    '';
-  };
-
-  repoSettings = cfg.settings.":mobilizon"."Mobilizon.Storage.Repo";
-  instanceSettings = cfg.settings.":mobilizon".":instance";
-
-  isLocalPostgres = repoSettings.socket_dir != null;
-
-  dbUser = if repoSettings.username != null then repoSettings.username else "mobilizon";
-
-  postgresql = config.services.postgresql.package;
-  postgresqlSocketDir = "/var/run/postgresql";
-
-  secretEnvFile = "/var/lib/mobilizon/secret-env.sh";
-in
-{
-  options = {
-    services.mobilizon = {
-      enable = mkEnableOption "Mobilizon federated organization and mobilization platform";
-
-      nginx.enable = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = ''
-          Whether an Nginx virtual host should be
-          set up to serve Mobilizon.
-        '';
-      };
-
-      package = mkPackageOption pkgs "mobilizon" { };
-
-      settings = mkOption {
-        type =
-          let
-            elixirTypes = settingsFormat.lib.types;
-          in
-          types.submodule {
-            freeformType = settingsFormat.type;
-
-            options = {
-              ":mobilizon" = {
-
-                "Mobilizon.Web.Endpoint" = {
-                  url.host = mkOption {
-                    type = elixirTypes.str;
-                    defaultText = lib.literalMD ''
-                      ''${settings.":mobilizon".":instance".hostname}
-                    '';
-                    description = ''
-                      Your instance's hostname for generating URLs throughout the app
-                    '';
-                  };
-
-                  http = {
-                    port = mkOption {
-                      type = elixirTypes.port;
-                      default = 4000;
-                      description = ''
-                        The port to run the server
-                      '';
-                    };
-                    ip = mkOption {
-                      type = elixirTypes.tuple;
-                      default = settingsFormat.lib.mkTuple [ 0 0 0 0 0 0 0 1 ];
-                      description = ''
-                        The IP address to listen on. Defaults to [::1] notated as a byte tuple.
-                      '';
-                    };
-                  };
-
-                  has_reverse_proxy = mkOption {
-                    type = elixirTypes.bool;
-                    default = true;
-                    description = ''
-                      Whether you use a reverse proxy
-                    '';
-                  };
-                };
-
-                ":instance" = {
-                  name = mkOption {
-                    type = elixirTypes.str;
-                    description = ''
-                      The fallback instance name if not configured into the admin UI
-                    '';
-                  };
-
-                  hostname = mkOption {
-                    type = elixirTypes.str;
-                    description = ''
-                      Your instance's hostname
-                    '';
-                  };
-
-                  email_from = mkOption {
-                    type = elixirTypes.str;
-                    defaultText = literalExpression ''
-                      noreply@''${settings.":mobilizon".":instance".hostname}
-                    '';
-                    description = ''
-                      The email for the From: header in emails
-                    '';
-                  };
-
-                  email_reply_to = mkOption {
-                    type = elixirTypes.str;
-                    defaultText = literalExpression ''
-                      ''${email_from}
-                    '';
-                    description = ''
-                      The email for the Reply-To: header in emails
-                    '';
-                  };
-                };
-
-                "Mobilizon.Storage.Repo" = {
-                  socket_dir = mkOption {
-                    type = types.nullOr elixirTypes.str;
-                    default = postgresqlSocketDir;
-                    description = ''
-                      Path to the postgres socket directory.
-
-                      Set this to null if you want to connect to a remote database.
-
-                      If non-null, the local PostgreSQL server will be configured with
-                      the configured database, permissions, and required extensions.
-
-                      If connecting to a remote database, please follow the
-                      instructions on how to setup your database:
-                      <https://docs.joinmobilizon.org/administration/install/release/#database-setup>
-                    '';
-                  };
-
-                  username = mkOption {
-                    type = types.nullOr elixirTypes.str;
-                    default = user;
-                    description = ''
-                      User used to connect to the database
-                    '';
-                  };
-
-                  database = mkOption {
-                    type = types.nullOr elixirTypes.str;
-                    default = "mobilizon_prod";
-                    description = ''
-                      Name of the database
-                    '';
-                  };
-                };
-              };
-            };
-          };
-        default = { };
-
-        description = ''
-          Mobilizon Elixir documentation, see
-          <https://docs.joinmobilizon.org/administration/configure/reference/>
-          for supported values.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    assertions = [
-      {
-        assertion = cfg.nginx.enable -> (cfg.settings.":mobilizon"."Mobilizon.Web.Endpoint".http.ip == settingsFormat.lib.mkTuple [ 0 0 0 0 0 0 0 1 ]);
-        message = "Setting the IP mobilizon listens on is only possible when the nginx config is not used, as it is hardcoded there.";
-      }
-    ];
-
-    services.mobilizon.settings = {
-      ":mobilizon" = {
-        "Mobilizon.Web.Endpoint" = {
-          server = true;
-          url.host = mkDefault instanceSettings.hostname;
-          secret_key_base =
-            settingsFormat.lib.mkGetEnv { envVariable = "MOBILIZON_INSTANCE_SECRET"; };
-        };
-
-        "Mobilizon.Web.Auth.Guardian".secret_key =
-          settingsFormat.lib.mkGetEnv { envVariable = "MOBILIZON_AUTH_SECRET"; };
-
-        ":instance" = {
-          registrations_open = mkDefault false;
-          demo = mkDefault false;
-          email_from = mkDefault "noreply@${instanceSettings.hostname}";
-          email_reply_to = mkDefault instanceSettings.email_from;
-        };
-
-        "Mobilizon.Storage.Repo" = {
-          # Forced by upstream since it uses PostgreSQL-specific extensions
-          adapter = settingsFormat.lib.mkAtom "Ecto.Adapters.Postgres";
-          pool_size = mkDefault 10;
-        };
-      };
-
-      ":tzdata".":data_dir" = "/var/lib/mobilizon/tzdata/";
-    };
-
-    # This somewhat follows upstream's systemd service here:
-    # https://framagit.org/framasoft/mobilizon/-/blob/master/support/systemd/mobilizon.service
-    systemd.services.mobilizon = {
-      description = "Mobilizon federated organization and mobilization platform";
-
-      wantedBy = [ "multi-user.target" ];
-
-      path = with pkgs; [
-        gawk
-        imagemagick
-        libwebp
-        file
-
-        # Optional:
-        gifsicle
-        jpegoptim
-        optipng
-        pngquant
-      ];
-
-      serviceConfig = {
-        ExecStartPre = "${launchers}/bin/mobilizon_ctl migrate";
-        ExecStart = "${launchers}/bin/mobilizon start";
-        ExecStop = "${launchers}/bin/mobilizon stop";
-
-        User = user;
-        Group = group;
-
-        StateDirectory = "mobilizon";
-
-        Restart = "on-failure";
-
-        PrivateTmp = true;
-        ProtectSystem = "full";
-        NoNewPrivileges = true;
-
-        ReadWritePaths = mkIf isLocalPostgres postgresqlSocketDir;
-      };
-    };
-
-    # Create the needed secrets before running Mobilizon, so that they are not
-    # in the nix store
-    #
-    # Since some of these tasks are quite common for Elixir projects (COOKIE for
-    # every BEAM project, Phoenix and Guardian are also quite common), this
-    # service could be abstracted in the future, and used by other Elixir
-    # projects.
-    systemd.services.mobilizon-setup-secrets = {
-      description = "Mobilizon setup secrets";
-      before = [ "mobilizon.service" ];
-      wantedBy = [ "mobilizon.service" ];
-
-      script =
-        let
-          # Taken from here:
-          # https://framagit.org/framasoft/mobilizon/-/blob/1.0.7/lib/mix/tasks/mobilizon/instance.ex#L132-133
-          genSecret =
-            "IO.puts(:crypto.strong_rand_bytes(64)" +
-            "|> Base.encode64()" +
-            "|> binary_part(0, 64))";
-
-          # Taken from here:
-          # https://github.com/elixir-lang/elixir/blob/v1.11.3/lib/mix/lib/mix/release.ex#L499
-          genCookie = "IO.puts(Base.encode32(:crypto.strong_rand_bytes(32)))";
-
-          evalElixir = str: ''
-            ${cfg.package.elixirPackage}/bin/elixir --eval '${str}'
-          '';
-        in
-        ''
-          set -euxo pipefail
-
-          if [ ! -f "${secretEnvFile}" ]; then
-            install -m 600 /dev/null "${secretEnvFile}"
-            cat > "${secretEnvFile}" <<EOF
-          # This file was automatically generated by mobilizon-setup-secrets.service
-          export MOBILIZON_AUTH_SECRET='$(${evalElixir genSecret})'
-          export MOBILIZON_INSTANCE_SECRET='$(${evalElixir genSecret})'
-          export RELEASE_COOKIE='$(${evalElixir genCookie})'
-          EOF
-          fi
-        '';
-
-      serviceConfig = {
-        Type = "oneshot";
-        User = user;
-        Group = group;
-        StateDirectory = "mobilizon";
-      };
-    };
-
-    # Add the required PostgreSQL extensions to the local PostgreSQL server,
-    # if local PostgreSQL is configured.
-    systemd.services.mobilizon-postgresql = mkIf isLocalPostgres {
-      description = "Mobilizon PostgreSQL setup";
-
-      after = [ "postgresql.service" ];
-      before = [ "mobilizon.service" "mobilizon-setup-secrets.service" ];
-      wantedBy = [ "mobilizon.service" ];
-
-      path = [ postgresql ];
-
-      # Taken from here:
-      # https://framagit.org/framasoft/mobilizon/-/blob/1.1.0/priv/templates/setup_db.eex
-      # TODO(to maintainers of mobilizon): the owner database alteration is necessary
-      # as PostgreSQL 15 changed their behaviors w.r.t. to privileges.
-      # See https://github.com/NixOS/nixpkgs/issues/216989 to get rid
-      # of that workaround.
-      script =
-        ''
-          psql "${repoSettings.database}" -c "\
-            CREATE EXTENSION IF NOT EXISTS postgis; \
-            CREATE EXTENSION IF NOT EXISTS pg_trgm; \
-            CREATE EXTENSION IF NOT EXISTS unaccent;"
-          psql -tAc 'ALTER DATABASE "${repoSettings.database}" OWNER TO "${dbUser}";'
-
-        '';
-
-      serviceConfig = {
-        Type = "oneshot";
-        User = config.services.postgresql.superUser;
-      };
-    };
-
-    systemd.tmpfiles.rules = [
-      "d /var/lib/mobilizon/uploads/exports/csv 700 mobilizon mobilizon - -"
-      "Z /var/lib/mobilizon 700 mobilizon mobilizon - -"
-    ];
-
-    services.postgresql = mkIf isLocalPostgres {
-      enable = true;
-      ensureDatabases = [ repoSettings.database ];
-      ensureUsers = [
-        {
-          name = dbUser;
-          # Given that `dbUser` is potentially arbitrarily custom, we will perform
-          # manual fixups in mobilizon-postgres.
-          # TODO(to maintainers of mobilizon): Feel free to simplify your setup by using `ensureDBOwnership`.
-          ensureDBOwnership = false;
-        }
-      ];
-      extraPlugins = ps: with ps; [ postgis ];
-    };
-
-    # Nginx config taken from support/nginx/mobilizon-release.conf
-    services.nginx =
-      let
-        inherit (cfg.settings.":mobilizon".":instance") hostname;
-        proxyPass = "http://[::1]:"
-          + toString cfg.settings.":mobilizon"."Mobilizon.Web.Endpoint".http.port;
-      in
-      lib.mkIf cfg.nginx.enable {
-        enable = true;
-        virtualHosts."${hostname}" = {
-          enableACME = lib.mkDefault true;
-          forceSSL = lib.mkDefault true;
-          extraConfig = ''
-            proxy_http_version 1.1;
-            proxy_set_header Upgrade $http_upgrade;
-            proxy_set_header Connection "upgrade";
-            proxy_set_header Host $host;
-            proxy_set_header X-Real-IP $remote_addr;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-            proxy_set_header X-Forwarded-Proto $scheme;
-          '';
-          locations."/" = {
-            inherit proxyPass;
-          };
-          locations."~ ^/(js|css|img)" = {
-            root = "${cfg.package}/lib/mobilizon-${cfg.package.version}/priv/static";
-            extraConfig = ''
-              etag off;
-              access_log off;
-              add_header Cache-Control "public, max-age=31536000, immutable";
-            '';
-          };
-          locations."~ ^/(media|proxy)" = {
-            inherit proxyPass;
-            extraConfig = ''
-              etag off;
-              access_log off;
-              add_header Cache-Control "public, max-age=31536000, immutable";
-            '';
-          };
-        };
-      };
-
-    users.users.${user} = {
-      description = "Mobilizon daemon user";
-      group = group;
-      isSystemUser = true;
-    };
-
-    users.groups.${group} = { };
-
-    # So that we have the `mobilizon` and `mobilizon_ctl` commands.
-    # The `mobilizon remote` command is useful for dropping a shell into the
-    # running Mobilizon instance, and `mobilizon_ctl` is used for common
-    # management tasks (e.g. adding users).
-    environment.systemPackages = [ launchers ];
-  };
-
-  meta.maintainers = with lib.maintainers; [ minijackson erictapen ];
-}
diff --git a/nixos/modules/services/web-apps/monica.nix b/nixos/modules/services/web-apps/monica.nix
deleted file mode 100644
index 6774e2c9bb46..000000000000
--- a/nixos/modules/services/web-apps/monica.nix
+++ /dev/null
@@ -1,468 +0,0 @@
-{
-  config,
-  lib,
-  pkgs,
-  ...
-}:
-with lib; let
-  cfg = config.services.monica;
-  monica = pkgs.monica.override {
-    dataDir = cfg.dataDir;
-  };
-  db = cfg.database;
-  mail = cfg.mail;
-
-  user = cfg.user;
-  group = cfg.group;
-
-  # shell script for local administration
-  artisan = pkgs.writeScriptBin "monica" ''
-    #! ${pkgs.runtimeShell}
-    cd ${monica}
-    sudo() {
-      if [[ "$USER" != ${user} ]]; then
-        exec /run/wrappers/bin/sudo -u ${user} "$@"
-      else
-        exec "$@"
-      fi
-    }
-    sudo ${pkgs.php}/bin/php artisan "$@"
-  '';
-
-  tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME;
-in {
-  options.services.monica = {
-    enable = mkEnableOption "monica";
-
-    user = mkOption {
-      default = "monica";
-      description = "User monica runs as.";
-      type = types.str;
-    };
-
-    group = mkOption {
-      default = "monica";
-      description = "Group monica runs as.";
-      type = types.str;
-    };
-
-    appKeyFile = mkOption {
-      description = ''
-        A file containing the Laravel APP_KEY - a 32 character long,
-        base64 encoded key used for encryption where needed. Can be
-        generated with <code>head -c 32 /dev/urandom | base64</code>.
-      '';
-      example = "/run/keys/monica-appkey";
-      type = types.path;
-    };
-
-    hostname = lib.mkOption {
-      type = lib.types.str;
-      default =
-        if config.networking.domain != null
-        then config.networking.fqdn
-        else config.networking.hostName;
-      defaultText = lib.literalExpression "config.networking.fqdn";
-      example = "monica.example.com";
-      description = ''
-        The hostname to serve monica on.
-      '';
-    };
-
-    appURL = mkOption {
-      description = ''
-        The root URL that you want to host monica on. All URLs in monica will be generated using this value.
-        If you change this in the future you may need to run a command to update stored URLs in the database.
-        Command example: <code>php artisan monica:update-url https://old.example.com https://new.example.com</code>
-      '';
-      default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
-      defaultText = ''http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostname}'';
-      example = "https://example.com";
-      type = types.str;
-    };
-
-    dataDir = mkOption {
-      description = "monica data directory";
-      default = "/var/lib/monica";
-      type = types.path;
-    };
-
-    database = {
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Database host address.";
-      };
-      port = mkOption {
-        type = types.port;
-        default = 3306;
-        description = "Database host port.";
-      };
-      name = mkOption {
-        type = types.str;
-        default = "monica";
-        description = "Database name.";
-      };
-      user = mkOption {
-        type = types.str;
-        default = user;
-        defaultText = lib.literalExpression "user";
-        description = "Database username.";
-      };
-      passwordFile = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        example = "/run/keys/monica-dbpassword";
-        description = ''
-          A file containing the password corresponding to
-          <option>database.user</option>.
-        '';
-      };
-      createLocally = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Create the database and database user locally.";
-      };
-    };
-
-    mail = {
-      driver = mkOption {
-        type = types.enum ["smtp" "sendmail"];
-        default = "smtp";
-        description = "Mail driver to use.";
-      };
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Mail host address.";
-      };
-      port = mkOption {
-        type = types.port;
-        default = 1025;
-        description = "Mail host port.";
-      };
-      fromName = mkOption {
-        type = types.str;
-        default = "monica";
-        description = "Mail \"from\" name.";
-      };
-      from = mkOption {
-        type = types.str;
-        default = "mail@monica.com";
-        description = "Mail \"from\" email.";
-      };
-      user = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        example = "monica";
-        description = "Mail username.";
-      };
-      passwordFile = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        example = "/run/keys/monica-mailpassword";
-        description = ''
-          A file containing the password corresponding to
-          <option>mail.user</option>.
-        '';
-      };
-      encryption = mkOption {
-        type = with types; nullOr (enum ["tls"]);
-        default = null;
-        description = "SMTP encryption mechanism to use.";
-      };
-    };
-
-    maxUploadSize = mkOption {
-      type = types.str;
-      default = "18M";
-      example = "1G";
-      description = "The maximum size for uploads (e.g. images).";
-    };
-
-    poolConfig = mkOption {
-      type = with types; attrsOf (oneOf [str int bool]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for the monica PHP pool. See the documentation on <literal>php-fpm.conf</literal>
-        for details on configuration directives.
-      '';
-    };
-
-    nginx = mkOption {
-      type = types.submodule (
-        recursiveUpdate
-        (import ../web-servers/nginx/vhost-options.nix {inherit config lib;}) {}
-      );
-      default = {};
-      example = ''
-        {
-          serverAliases = [
-            "monica.''${config.networking.domain}"
-          ];
-          # To enable encryption and let let's encrypt take care of certificate
-          forceSSL = true;
-          enableACME = true;
-        }
-      '';
-      description = ''
-        With this option, you can customize the nginx virtualHost settings.
-      '';
-    };
-
-    config = mkOption {
-      type = with types;
-        attrsOf
-        (nullOr
-          (either
-            (oneOf [
-              bool
-              int
-              port
-              path
-              str
-            ])
-            (submodule {
-              options = {
-                _secret = mkOption {
-                  type = nullOr str;
-                  description = ''
-                    The path to a file containing the value the
-                    option should be set to in the final
-                    configuration file.
-                  '';
-                };
-              };
-            })));
-      default = {};
-      example = ''
-        {
-          ALLOWED_IFRAME_HOSTS = "https://example.com";
-          WKHTMLTOPDF = "/home/user/bins/wkhtmltopdf";
-          AUTH_METHOD = "oidc";
-          OIDC_NAME = "MyLogin";
-          OIDC_DISPLAY_NAME_CLAIMS = "name";
-          OIDC_CLIENT_ID = "monica";
-          OIDC_CLIENT_SECRET = {_secret = "/run/keys/oidc_secret"};
-          OIDC_ISSUER = "https://keycloak.example.com/auth/realms/My%20Realm";
-          OIDC_ISSUER_DISCOVER = true;
-        }
-      '';
-      description = ''
-        monica configuration options to set in the
-        <filename>.env</filename> file.
-
-        Refer to <link xlink:href="https://github.com/monicahq/monica"/>
-        for details on supported values.
-
-        Settings containing secret data should be set to an attribute
-        set containing the attribute <literal>_secret</literal> - a
-        string pointing to a file containing the value the option
-        should be set to. See the example to get a better picture of
-        this: in the resulting <filename>.env</filename> file, the
-        <literal>OIDC_CLIENT_SECRET</literal> key will be set to the
-        contents of the <filename>/run/keys/oidc_secret</filename>
-        file.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = db.createLocally -> db.user == user;
-        message = "services.monica.database.user must be set to ${user} if services.monica.database.createLocally is set true.";
-      }
-      {
-        assertion = db.createLocally -> db.passwordFile == null;
-        message = "services.monica.database.passwordFile cannot be specified if services.monica.database.createLocally is set to true.";
-      }
-    ];
-
-    services.monica.config = {
-      APP_ENV = "production";
-      APP_KEY._secret = cfg.appKeyFile;
-      APP_URL = cfg.appURL;
-      DB_HOST = db.host;
-      DB_PORT = db.port;
-      DB_DATABASE = db.name;
-      DB_USERNAME = db.user;
-      MAIL_DRIVER = mail.driver;
-      MAIL_FROM_NAME = mail.fromName;
-      MAIL_FROM = mail.from;
-      MAIL_HOST = mail.host;
-      MAIL_PORT = mail.port;
-      MAIL_USERNAME = mail.user;
-      MAIL_ENCRYPTION = mail.encryption;
-      DB_PASSWORD._secret = db.passwordFile;
-      MAIL_PASSWORD._secret = mail.passwordFile;
-      APP_SERVICES_CACHE = "/run/monica/cache/services.php";
-      APP_PACKAGES_CACHE = "/run/monica/cache/packages.php";
-      APP_CONFIG_CACHE = "/run/monica/cache/config.php";
-      APP_ROUTES_CACHE = "/run/monica/cache/routes-v7.php";
-      APP_EVENTS_CACHE = "/run/monica/cache/events.php";
-      SESSION_SECURE_COOKIE = tlsEnabled;
-    };
-
-    environment.systemPackages = [artisan];
-
-    services.mysql = mkIf db.createLocally {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = [db.name];
-      ensureUsers = [
-        {
-          name = db.user;
-          ensurePermissions = {"${db.name}.*" = "ALL PRIVILEGES";};
-        }
-      ];
-    };
-
-    services.phpfpm.pools.monica = {
-      inherit user group;
-      phpOptions = ''
-        log_errors = on
-        post_max_size = ${cfg.maxUploadSize}
-        upload_max_filesize = ${cfg.maxUploadSize}
-      '';
-      settings = {
-        "listen.mode" = "0660";
-        "listen.owner" = user;
-        "listen.group" = group;
-      } // cfg.poolConfig;
-    };
-
-    services.nginx = {
-      enable = mkDefault true;
-      recommendedTlsSettings = true;
-      recommendedOptimisation = true;
-      recommendedGzipSettings = true;
-      recommendedBrotliSettings = true;
-      recommendedProxySettings = true;
-      virtualHosts.${cfg.hostname} = mkMerge [
-        cfg.nginx
-        {
-          root = mkForce "${monica}/public";
-          locations = {
-            "/" = {
-              index = "index.php";
-              tryFiles = "$uri $uri/ /index.php?$query_string";
-            };
-            "~ \.php$".extraConfig = ''
-              fastcgi_pass unix:${config.services.phpfpm.pools."monica".socket};
-            '';
-            "~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
-              extraConfig = "expires 365d;";
-            };
-          };
-        }
-      ];
-    };
-
-    systemd.services.monica-setup = {
-      description = "Preparation tasks for monica";
-      before = ["phpfpm-monica.service"];
-      after = optional db.createLocally "mysql.service";
-      wantedBy = ["multi-user.target"];
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = true;
-        User = user;
-        UMask = 077;
-        WorkingDirectory = "${monica}";
-        RuntimeDirectory = "monica/cache";
-        RuntimeDirectoryMode = 0700;
-      };
-      path = [pkgs.replace-secret];
-      script = let
-        isSecret = v: isAttrs v && v ? _secret && isString v._secret;
-        monicaEnvVars = lib.generators.toKeyValue {
-          mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
-            mkValueString = v:
-              with builtins;
-                if isInt v
-                then toString v
-                else if isString v
-                then v
-                else if true == v
-                then "true"
-                else if false == v
-                then "false"
-                else if isSecret v
-                then hashString "sha256" v._secret
-                else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
-          };
-        };
-        secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
-        mkSecretReplacement = file: ''
-          replace-secret ${escapeShellArgs [(builtins.hashString "sha256" file) file "${cfg.dataDir}/.env"]}
-        '';
-        secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
-        filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{} null])) cfg.config;
-        monicaEnv = pkgs.writeText "monica.env" (monicaEnvVars filteredConfig);
-      in ''
-        # error handling
-        set -euo pipefail
-
-        # create .env file
-        install -T -m 0600 -o ${user} ${monicaEnv} "${cfg.dataDir}/.env"
-        ${secretReplacements}
-        if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
-          sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
-        fi
-
-        # migrate & seed db
-        ${pkgs.php}/bin/php artisan key:generate --force
-        ${pkgs.php}/bin/php artisan setup:production -v --force
-      '';
-    };
-
-    systemd.services.monica-scheduler = {
-      description = "Background tasks for monica";
-      startAt = "minutely";
-      after = ["monica-setup.service"];
-      serviceConfig = {
-        Type = "oneshot";
-        User = user;
-        WorkingDirectory = "${monica}";
-        ExecStart = "${pkgs.php}/bin/php ${monica}/artisan schedule:run -v";
-      };
-    };
-
-    systemd.tmpfiles.rules = [
-      "d ${cfg.dataDir}                            0710 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public                     0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads             0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage                    0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/app                0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/fonts              0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework          0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework/cache    0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework/views    0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/logs               0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/uploads            0700 ${user} ${group} - -"
-    ];
-
-    users = {
-      users = mkIf (user == "monica") {
-        monica = {
-          inherit group;
-          isSystemUser = true;
-        };
-        "${config.services.nginx.user}".extraGroups = [group];
-      };
-      groups = mkIf (group == "monica") {
-        monica = {};
-      };
-    };
-  };
-}
-
diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
deleted file mode 100644
index 7e2d59d3c3e7..000000000000
--- a/nixos/modules/services/web-apps/moodle.nix
+++ /dev/null
@@ -1,314 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  inherit (lib) mkDefault mkEnableOption mkPackageOption mkForce mkIf mkMerge mkOption types;
-  inherit (lib) concatStringsSep literalExpression mapAttrsToList optional optionalString;
-
-  cfg = config.services.moodle;
-  fpm = config.services.phpfpm.pools.moodle;
-
-  user = "moodle";
-  group = config.services.httpd.group;
-  stateDir = "/var/lib/moodle";
-
-  moodleConfig = pkgs.writeText "config.php" ''
-  <?php  // Moodle configuration file
-
-  unset($CFG);
-  global $CFG;
-  $CFG = new stdClass();
-
-  $CFG->dbtype    = '${ { mysql = "mariadb"; pgsql = "pgsql"; }.${cfg.database.type} }';
-  $CFG->dblibrary = 'native';
-  $CFG->dbhost    = '${cfg.database.host}';
-  $CFG->dbname    = '${cfg.database.name}';
-  $CFG->dbuser    = '${cfg.database.user}';
-  ${optionalString (cfg.database.passwordFile != null) "$CFG->dbpass = file_get_contents('${cfg.database.passwordFile}');"}
-  $CFG->prefix    = 'mdl_';
-  $CFG->dboptions = array (
-    'dbpersist' => 0,
-    'dbport' => '${toString cfg.database.port}',
-    ${optionalString (cfg.database.socket != null) "'dbsocket' => '${cfg.database.socket}',"}
-    'dbcollation' => 'utf8mb4_unicode_ci',
-  );
-
-  $CFG->wwwroot   = '${if cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL then "https" else "http"}://${cfg.virtualHost.hostName}';
-  $CFG->dataroot  = '${stateDir}';
-  $CFG->admin     = 'admin';
-
-  $CFG->directorypermissions = 02777;
-  $CFG->disableupdateautodeploy = true;
-
-  $CFG->pathtogs = '${pkgs.ghostscript}/bin/gs';
-  $CFG->pathtophp = '${phpExt}/bin/php';
-  $CFG->pathtodu = '${pkgs.coreutils}/bin/du';
-  $CFG->aspellpath = '${pkgs.aspell}/bin/aspell';
-  $CFG->pathtodot = '${pkgs.graphviz}/bin/dot';
-
-  ${cfg.extraConfig}
-
-  require_once('${cfg.package}/share/moodle/lib/setup.php');
-
-  // There is no php closing tag in this file,
-  // it is intentional because it prevents trailing whitespace problems!
-  '';
-
-  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
-  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
-
-  phpExt = pkgs.php81.buildEnv {
-    extensions = { all, ... }: with all; [ iconv mbstring curl openssl tokenizer soap ctype zip gd simplexml dom intl sqlite3 pgsql pdo_sqlite pdo_pgsql pdo_odbc pdo_mysql pdo mysqli session zlib xmlreader fileinfo filter opcache exif sodium ];
-    extraConfig = "max_input_vars = 5000";
-  };
-in
-{
-  # interface
-  options.services.moodle = {
-    enable = mkEnableOption "Moodle web application";
-
-    package = mkPackageOption pkgs "moodle" { };
-
-    initialPassword = mkOption {
-      type = types.str;
-      example = "correcthorsebatterystaple";
-      description = ''
-        Specifies the initial password for the admin, i.e. the password assigned if the user does not already exist.
-        The password specified here is world-readable in the Nix store, so it should be changed promptly.
-      '';
-    };
-
-    database = {
-      type = mkOption {
-        type = types.enum [ "mysql" "pgsql" ];
-        default = "mysql";
-        description = "Database engine to use.";
-      };
-
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Database host address.";
-      };
-
-      port = mkOption {
-        type = types.port;
-        description = "Database host port.";
-        default = {
-          mysql = 3306;
-          pgsql = 5432;
-        }.${cfg.database.type};
-        defaultText = literalExpression "3306";
-      };
-
-      name = mkOption {
-        type = types.str;
-        default = "moodle";
-        description = "Database name.";
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "moodle";
-        description = "Database user.";
-      };
-
-      passwordFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        example = "/run/keys/moodle-dbpassword";
-        description = ''
-          A file containing the password corresponding to
-          {option}`database.user`.
-        '';
-      };
-
-      socket = mkOption {
-        type = types.nullOr types.path;
-        default =
-          if mysqlLocal then "/run/mysqld/mysqld.sock"
-          else if pgsqlLocal then "/run/postgresql"
-          else null;
-        defaultText = literalExpression "/run/mysqld/mysqld.sock";
-        description = "Path to the unix socket file to use for authentication.";
-      };
-
-      createLocally = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Create the database and database user locally.";
-      };
-    };
-
-    virtualHost = mkOption {
-      type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-      example = literalExpression ''
-        {
-          hostName = "moodle.example.org";
-          adminAddr = "webmaster@example.org";
-          forceSSL = true;
-          enableACME = true;
-        }
-      '';
-      description = ''
-        Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`.
-        See [](#opt-services.httpd.virtualHosts) for further information.
-      '';
-    };
-
-    poolConfig = mkOption {
-      type = with types; attrsOf (oneOf [ str int bool ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for the Moodle PHP pool. See the documentation on `php-fpm.conf`
-        for details on configuration directives.
-      '';
-    };
-
-    extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-      description = ''
-        Any additional text to be appended to the config.php
-        configuration file. This is a PHP script. For configuration
-        details, see <https://docs.moodle.org/37/en/Configuration_file>.
-      '';
-      example = ''
-        $CFG->disableupdatenotifications = true;
-      '';
-    };
-  };
-
-  # implementation
-  config = mkIf cfg.enable {
-
-    assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.user == cfg.database.name;
-        message = "services.moodle.database.user must be set to ${user} if services.moodle.database.createLocally is set true";
-      }
-      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
-        message = "a password cannot be specified if services.moodle.database.createLocally is set to true";
-      }
-    ];
-
-    services.mysql = mkIf mysqlLocal {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        { name = cfg.database.user;
-          ensurePermissions = {
-            "${cfg.database.name}.*" = "SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER";
-          };
-        }
-      ];
-    };
-
-    services.postgresql = mkIf pgsqlLocal {
-      enable = true;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        { name = cfg.database.user;
-          ensureDBOwnership = true;
-        }
-      ];
-    };
-
-    services.phpfpm.pools.moodle = {
-      inherit user group;
-      phpPackage = phpExt;
-      phpEnv.MOODLE_CONFIG = "${moodleConfig}";
-      phpOptions = ''
-        zend_extension = opcache.so
-        opcache.enable = 1
-        max_input_vars = 5000
-      '';
-      settings = {
-        "listen.owner" = config.services.httpd.user;
-        "listen.group" = config.services.httpd.group;
-      } // cfg.poolConfig;
-    };
-
-    services.httpd = {
-      enable = true;
-      adminAddr = mkDefault cfg.virtualHost.adminAddr;
-      extraModules = [ "proxy_fcgi" ];
-      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
-        documentRoot = mkForce "${cfg.package}/share/moodle";
-        extraConfig = ''
-          <Directory "${cfg.package}/share/moodle">
-            <FilesMatch "\.php$">
-              <If "-f %{REQUEST_FILENAME}">
-                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
-              </If>
-            </FilesMatch>
-            Options -Indexes
-            DirectoryIndex index.php
-          </Directory>
-        '';
-      } ];
-    };
-
-    systemd.tmpfiles.settings."10-moodle".${stateDir}.d = {
-      inherit user group;
-      mode = "0750";
-    };
-
-    systemd.services.moodle-init = {
-      wantedBy = [ "multi-user.target" ];
-      before = [ "phpfpm-moodle.service" ];
-      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
-      environment.MOODLE_CONFIG = moodleConfig;
-      script = ''
-        ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/check_database_schema.php && rc=$? || rc=$?
-
-        [ "$rc" == 1 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/upgrade.php \
-          --non-interactive \
-          --allow-unstable
-
-        [ "$rc" == 2 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/install_database.php \
-          --agree-license \
-          --adminpass=${cfg.initialPassword}
-
-        true
-      '';
-      serviceConfig = {
-        User = user;
-        Group = group;
-        Type = "oneshot";
-      };
-    };
-
-    systemd.services.moodle-cron = {
-      description = "Moodle cron service";
-      after = [ "moodle-init.service" ];
-      environment.MOODLE_CONFIG = moodleConfig;
-      serviceConfig = {
-        User = user;
-        Group = group;
-        ExecStart = "${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/cron.php";
-      };
-    };
-
-    systemd.timers.moodle-cron = {
-      description = "Moodle cron timer";
-      wantedBy = [ "timers.target" ];
-      timerConfig = {
-        OnCalendar = "minutely";
-      };
-    };
-
-    systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
-
-    users.users.${user} = {
-      group = group;
-      isSystemUser = true;
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/movim.nix b/nixos/modules/services/web-apps/movim.nix
deleted file mode 100644
index 29bed0e067fa..000000000000
--- a/nixos/modules/services/web-apps/movim.nix
+++ /dev/null
@@ -1,709 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  inherit (lib)
-    filterAttrsRecursive
-    generators
-    literalExpression
-    mkDefault
-    mkIf
-    mkOption
-    mkEnableOption
-    mkPackageOption
-    mkMerge
-    pipe
-    types
-    ;
-
-  cfg = config.services.movim;
-
-  defaultPHPCfg = {
-    "output_buffering" = 0;
-    "error_reporting" = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
-    "opcache.enable_cli" = 1;
-    "opcache.interned_strings_buffer" = 8;
-    "opcache.max_accelerated_files" = 6144;
-    "opcache.memory_consumption" = 128;
-    "opcache.revalidate_freq" = 2;
-    "opcache.fast_shutdown" = 1;
-  };
-
-  phpCfg = generators.toKeyValue
-    { mkKeyValue = generators.mkKeyValueDefault { } " = "; }
-    (defaultPHPCfg // cfg.phpCfg);
-
-  podConfigFlags =
-    let
-      bevalue = a: lib.escapeShellArg (generators.mkValueStringDefault { } a);
-    in
-    lib.concatStringsSep " "
-      (lib.attrsets.foldlAttrs
-        (acc: k: v: acc ++ lib.optional (v != null) "--${k}=${bevalue v}")
-        [ ]
-        cfg.podConfig);
-
-  package =
-    let
-      p = cfg.package.override
-        ({
-          inherit phpCfg;
-          withPgsql = cfg.database.type == "pgsql";
-          withMysql = cfg.database.type == "mysql";
-          inherit (cfg) minifyStaticFiles;
-        } // lib.optionalAttrs (lib.isAttrs cfg.minifyStaticFiles) (with cfg.minifyStaticFiles; {
-          esbuild = esbuild.package;
-          lightningcss = lightningcss.package;
-          scour = scour.package;
-        }));
-    in
-    p.overrideAttrs (finalAttrs: prevAttrs:
-      let
-        appDir = "$out/share/php/${finalAttrs.pname}";
-
-        stateDirectories = ''
-          # Symlinking in our state directories
-          rm -rf $out/.env $out/cache ${appDir}/public/cache
-          ln -s ${cfg.dataDir}/.env ${appDir}/.env
-          ln -s ${cfg.dataDir}/public/cache ${appDir}/public/cache
-          ln -s ${cfg.logDir} ${appDir}/log
-          ln -s ${cfg.runtimeDir}/cache ${appDir}/cache
-        '';
-
-        exposeComposer = ''
-          # Expose PHP Composer for scripts
-          mkdir -p $out/bin
-          echo "#!${lib.getExe pkgs.dash}" > $out/bin/movim-composer
-          echo "${finalAttrs.php.packages.composer}/bin/composer --working-dir="${appDir}" \"\$@\"" >> $out/bin/movim-composer
-          chmod +x $out/bin/movim-composer
-        '';
-
-        podConfigInputDisableReplace = lib.optionalString (podConfigFlags != "")
-          (lib.concatStringsSep "\n"
-            (lib.attrsets.foldlAttrs
-              (acc: k: v:
-                acc ++ lib.optional (v != null)
-                  # Disable all Admin panel options that were set in the
-                  # `cfg.podConfig` to prevent confusing situtions where the
-                  # values are rewritten on server reboot
-                  ''
-                    substituteInPlace ${appDir}/app/widgets/AdminMain/adminmain.tpl \
-                      --replace-warn 'name="${k}"' 'name="${k}" disabled'
-                  '')
-              [ ]
-              cfg.podConfig));
-
-        precompressStaticFilesJobs =
-          let
-            inherit (cfg.precompressStaticFiles) brotli gzip;
-
-            findTextFileNames = lib.concatStringsSep " -o "
-              (builtins.map (n: ''-iname "*.${n}"'')
-                [ "css" "ini" "js" "json" "manifest" "mjs" "svg" "webmanifest" ]);
-          in
-          lib.concatStringsSep "\n" [
-            (lib.optionalString brotli.enable ''
-              echo -n "Precompressing static files with Brotli …"
-              find ${appDir}/public -type f ${findTextFileNames} -print0 \
-                | xargs -0 -n 1 -P $NIX_BUILD_CORES ${pkgs.writeShellScript "movim_precompress_broti" ''
-                    file="$1"
-                    ${lib.getExe brotli.package} --keep --quality=${builtins.toString brotli.compressionLevel} --output=$file.br $file
-                  ''}
-              echo " done."
-            '')
-            (lib.optionalString gzip.enable ''
-              echo -n "Precompressing static files with Gzip …"
-              find ${appDir}/public -type f ${findTextFileNames} -print0 \
-                | xargs -0 -n 1 -P $NIX_BUILD_CORES ${pkgs.writeShellScript "movim_precompress_broti" ''
-                    file="$1"
-                    ${lib.getExe gzip.package} -c -${builtins.toString gzip.compressionLevel} $file > $file.gz
-                  ''}
-              echo " done."
-            '')
-          ];
-      in
-      {
-        postInstall = lib.concatStringsSep "\n\n" [
-          prevAttrs.postInstall
-          stateDirectories
-          exposeComposer
-          podConfigInputDisableReplace
-          precompressStaticFilesJobs
-        ];
-      });
-
-  configFile = pipe cfg.settings [
-    (filterAttrsRecursive (_: v: v != null))
-    (generators.toKeyValue { })
-    (pkgs.writeText "movim-env")
-  ];
-
-  pool = "movim";
-  fpm = config.services.phpfpm.pools.${pool};
-  phpExecutionUnit = "phpfpm-${pool}";
-
-  dbService = {
-    "postgresql" = "postgresql.service";
-    "mysql" = "mysql.service";
-  }.${cfg.database.type};
-in
-{
-  options.services = {
-    movim = {
-      enable = mkEnableOption "a Movim instance";
-      package = mkPackageOption pkgs "movim" { };
-      phpPackage = mkPackageOption pkgs "php" { };
-
-      phpCfg = mkOption {
-        type = with types; attrsOf (oneOf [ int str bool ]);
-        defaultText = literalExpression (generators.toPretty { } defaultPHPCfg);
-        default = { };
-        description = "Extra PHP INI options such as `memory_limit`, `max_execution_time`, etc.";
-      };
-
-      user = mkOption {
-        type = types.nonEmptyStr;
-        default = "movim";
-        description = "User running Movim service";
-      };
-
-      group = mkOption {
-        type = types.nonEmptyStr;
-        default = "movim";
-        description = "Group running Movim service";
-      };
-
-      dataDir = mkOption {
-        type = types.nonEmptyStr;
-        default = "/var/lib/movim";
-        description = "State directory of the `movim` user which holds the application’s state & data.";
-      };
-
-      logDir = mkOption {
-        type = types.nonEmptyStr;
-        default = "/var/log/movim";
-        description = "Log directory of the `movim` user which holds the application’s logs.";
-      };
-
-      runtimeDir = mkOption {
-        type = types.nonEmptyStr;
-        default = "/run/movim";
-        description = "Runtime directory of the `movim` user which holds the application’s caches & temporary files.";
-      };
-
-      domain = mkOption {
-        type = types.nonEmptyStr;
-        description = "Fully-qualified domain name (FQDN) for the Movim instance.";
-      };
-
-      port = mkOption {
-        type = types.port;
-        default = 8080;
-        description = "Movim daemon port.";
-      };
-
-      debug = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Debugging logs.";
-      };
-
-      verbose = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Verbose logs.";
-      };
-
-      minifyStaticFiles = mkOption {
-        type = with types; either bool (submodule {
-          options = {
-            script = mkOption {
-              type = types.submodule {
-                options = {
-                  enable = mkEnableOption "Script minification";
-                  package = mkPackageOption pkgs "esbuild" { };
-                  target = mkOption {
-                    type = with types; nullOr nonEmptyStr;
-                    default = null;
-                  };
-                };
-              };
-            };
-            style = mkOption {
-              type = types.submodule {
-                options = {
-                  enable = mkEnableOption "Script minification";
-                  package = mkPackageOption pkgs "lightningcss" { };
-                  target = mkOption {
-                    type = with types; nullOr nonEmptyStr;
-                    default = null;
-                  };
-                };
-              };
-            };
-            svg = mkOption {
-              type = types.submodule {
-                options = {
-                  enable = mkEnableOption "SVG minification";
-                  package = mkPackageOption pkgs "scour" { };
-                };
-              };
-            };
-          };
-        });
-        default = true;
-        description = "Do minification on public static files";
-      };
-
-      precompressStaticFiles = mkOption {
-        type = with types; submodule {
-          options = {
-            brotli = {
-              enable = mkEnableOption "Brotli precompression";
-              package = mkPackageOption pkgs "brotli" { };
-              compressionLevel = mkOption {
-                type = types.ints.between 0 11;
-                default = 11;
-                description = "Brotli compression level";
-              };
-            };
-            gzip = {
-              enable = mkEnableOption "Gzip precompression";
-              package = mkPackageOption pkgs "gzip" { };
-              compressionLevel = mkOption {
-                type = types.ints.between 1 9;
-                default = 9;
-                description = "Gzip compression level";
-              };
-            };
-          };
-        };
-        default = {
-          brotli.enable = true;
-          gzip.enable = false;
-        };
-        description = "Aggressively precompress static files";
-      };
-
-      podConfig = mkOption {
-        type = types.submodule {
-          options = {
-            info = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = "Content of the info box on the login page";
-            };
-
-            description = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = "General description of the instance";
-            };
-
-            timezone = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = "The server timezone";
-            };
-
-            restrictsuggestions = mkOption {
-              type = with types; nullOr bool;
-              default = null;
-              description = "Only suggest chatrooms, Communities and other contents that are available on the user XMPP server and related services";
-            };
-
-            chatonly = mkOption {
-              type = with types; nullOr bool;
-              default = null;
-              description = "Disable all the social feature (Communities, Blog…) and keep only the chat ones";
-            };
-
-            disableregistration = mkOption {
-              type = with types; nullOr bool;
-              default = null;
-              description = "Remove the XMPP registration flow and buttons from the interface";
-            };
-
-            loglevel = mkOption {
-              type = with types; nullOr (ints.between 0 3);
-              default = null;
-              description = "The server loglevel";
-            };
-
-            locale = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = "The server main locale";
-            };
-
-            xmppdomain = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = "The default XMPP server domain";
-            };
-
-            xmppdescription = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = "The default XMPP server description";
-            };
-
-            xmppwhitelist = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = "The allowlisted XMPP servers";
-            };
-          };
-        };
-        default = { };
-        description = ''
-          Pod configuration (values from `php daemon.php config --help`).
-          Note that these values will now be disabled in the admin panel.
-        '';
-      };
-
-      settings = mkOption {
-        type = with types; attrsOf (nullOr (oneOf [ int str bool ]));
-        default = { };
-        description = ".env settings for Movim. Secrets should use `secretFile` option instead. `null`s will be culled.";
-      };
-
-      secretFile = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        description = "The secret file to be sourced for the .env settings.";
-      };
-
-      database = {
-        type = mkOption {
-          type = types.enum [ "mysql" "postgresql" ];
-          example = "mysql";
-          default = "postgresql";
-          description = "Database engine to use.";
-        };
-
-        name = mkOption {
-          type = types.str;
-          default = "movim";
-          description = "Database name.";
-        };
-
-        user = mkOption {
-          type = types.str;
-          default = "movim";
-          description = "Database username.";
-        };
-
-        createLocally = mkOption {
-          type = types.bool;
-          default = true;
-          description = "local database using UNIX socket authentication";
-        };
-      };
-
-      nginx = mkOption {
-        type = with types; nullOr (submodule
-          (import ../web-servers/nginx/vhost-options.nix {
-            inherit config lib;
-          }));
-        default = null;
-        example = lib.literalExpression /* nginx */ ''
-          {
-            serverAliases = [
-              "pics.''${config.networking.domain}"
-            ];
-            enableACME = true;
-            forceHttps = true;
-          }
-        '';
-        description = ''
-          With this option, you can customize an nginx virtual host which already has sensible defaults for Movim.
-          Set to `{ }` if you do not need any customization to the virtual host.
-          If enabled, then by default, the {option}`serverName` is `''${domain}`,
-          If this is set to null (the default), no nginx virtualHost will be configured.
-        '';
-      };
-
-      poolConfig = mkOption {
-        type = with types; attrsOf (oneOf [ int str bool ]);
-        default = { };
-        description = "Options for Movim’s PHP-FPM pool.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ cfg.package ];
-
-    users = {
-      users = {
-        movim = mkIf (cfg.user == "movim") {
-          isSystemUser = true;
-          group = cfg.group;
-        };
-        "${config.services.nginx.user}".extraGroups = [ cfg.group ];
-      };
-      groups = {
-        ${cfg.group} = { };
-      };
-    };
-
-    services = {
-      movim = {
-        settings = mkMerge [
-          {
-            DAEMON_URL = "//${cfg.domain}";
-            DAEMON_PORT = cfg.port;
-            DAEMON_INTERFACE = "127.0.0.1";
-            DAEMON_DEBUG = cfg.debug;
-            DAEMON_VERBOSE = cfg.verbose;
-          }
-          (mkIf cfg.database.createLocally {
-            DB_DRIVER = {
-              "postgresql" = "pgsql";
-              "mysql" = "mysql";
-            }.${cfg.database.type};
-            DB_HOST = "localhost";
-            DB_PORT = config.services.${cfg.database.type}.settings.port;
-            DB_DATABASE = cfg.database.name;
-            DB_USERNAME = cfg.database.user;
-            DB_PASSWORD = "";
-          })
-        ];
-
-        poolConfig = lib.mapAttrs' (n: v: lib.nameValuePair n (lib.mkDefault v)) {
-          "pm" = "dynamic";
-          "php_admin_value[error_log]" = "stderr";
-          "php_admin_flag[log_errors]" = true;
-          "catch_workers_output" = true;
-          "pm.max_children" = 32;
-          "pm.start_servers" = 2;
-          "pm.min_spare_servers" = 2;
-          "pm.max_spare_servers" = 8;
-          "pm.max_requests" = 500;
-        };
-      };
-
-      nginx = mkIf (cfg.nginx != null) {
-        enable = true;
-        recommendedOptimisation = true;
-        recommendedGzipSettings = true;
-        recommendedBrotliSettings = true;
-        recommendedProxySettings = true;
-        # TODO: recommended cache options already in Nginx⁇
-        appendHttpConfig = /* nginx */ ''
-          fastcgi_cache_path /tmp/nginx_cache levels=1:2 keys_zone=nginx_cache:100m inactive=60m;
-          fastcgi_cache_key "$scheme$request_method$host$request_uri";
-        '';
-        virtualHosts."${cfg.domain}" = mkMerge [
-          cfg.nginx
-          {
-            root = lib.mkForce "${package}/share/php/movim/public";
-            locations = {
-              "/favicon.ico" = {
-                priority = 100;
-                extraConfig = /* nginx */ ''
-                  access_log off;
-                  log_not_found off;
-                '';
-              };
-              "/robots.txt" = {
-                priority = 100;
-                extraConfig = /* nginx */ ''
-                  access_log off;
-                  log_not_found off;
-                '';
-              };
-              "~ /\\.(?!well-known).*" = {
-                priority = 210;
-                extraConfig = /* nginx */ ''
-                  deny all;
-                '';
-              };
-              # Ask nginx to cache every URL starting with "/picture"
-              "/picture" = {
-                priority = 400;
-                tryFiles = "$uri $uri/ /index.php$is_args$args";
-                extraConfig = /* nginx */ ''
-                  set $no_cache 0; # Enable cache only there
-                '';
-              };
-              "/" = {
-                priority = 490;
-                tryFiles = "$uri $uri/ /index.php$is_args$args";
-                extraConfig = /* nginx */ ''
-                  # https://github.com/movim/movim/issues/314
-                  add_header Content-Security-Policy "default-src 'self'; img-src 'self' aesgcm: https:; media-src 'self' aesgcm: https:; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';";
-                  set $no_cache 1;
-                '';
-              };
-              "~ \\.php$" = {
-                priority = 500;
-                tryFiles = "$uri =404";
-                extraConfig = /* nginx */ ''
-                  include ${config.services.nginx.package}/conf/fastcgi.conf;
-                  add_header X-Cache $upstream_cache_status;
-                  fastcgi_ignore_headers "Cache-Control" "Expires" "Set-Cookie";
-                  fastcgi_cache nginx_cache;
-                  fastcgi_cache_valid any 7d;
-                  fastcgi_cache_bypass $no_cache;
-                  fastcgi_no_cache $no_cache;
-                  fastcgi_split_path_info ^(.+\.php)(/.+)$;
-                  fastcgi_index index.php;
-                  fastcgi_pass unix:${fpm.socket};
-                '';
-              };
-              "/ws/" = {
-                priority = 900;
-                proxyPass = "http://${cfg.settings.DAEMON_INTERFACE}:${builtins.toString cfg.port}/";
-                proxyWebsockets = true;
-                recommendedProxySettings = true;
-                extraConfig = /* nginx */ ''
-                  proxy_set_header X-Forwarded-Proto $scheme;
-                  proxy_redirect off;
-                '';
-              };
-            };
-            extraConfig = /* ngnix */ ''
-              index index.php;
-            '';
-          }
-        ];
-      };
-
-      mysql = mkIf (cfg.database.createLocally && cfg.database.type == "mysql") {
-        enable = mkDefault true;
-        package = mkDefault pkgs.mariadb;
-        ensureDatabases = [ cfg.database.name ];
-        ensureUsers = [{
-          name = cfg.user;
-          ensureDBOwnership = true;
-        }];
-      };
-
-      postgresql = mkIf (cfg.database.createLocally && cfg.database.type == "postgresql") {
-        enable = mkDefault true;
-        ensureDatabases = [ cfg.database.name ];
-        ensureUsers = [{
-          name = cfg.user;
-          ensureDBOwnership = true;
-        }];
-        authentication = ''
-          host ${cfg.database.name} ${cfg.database.user} localhost trust
-        '';
-      };
-
-      phpfpm.pools.${pool} =
-        let
-          socketOwner =
-            if (cfg.nginx != null)
-            then config.services.nginx.user
-            else cfg.user;
-        in
-        {
-          phpPackage = package.php;
-          user = cfg.user;
-          group = cfg.group;
-
-          phpOptions = ''
-            error_log = 'stderr'
-            log_errors = on
-          '';
-
-          settings = {
-            "listen.owner" = socketOwner;
-            "listen.group" = cfg.group;
-            "listen.mode" = "0660";
-            "catch_workers_output" = true;
-          } // cfg.poolConfig;
-        };
-    };
-
-    systemd = {
-      services.movim-data-setup = {
-        description = "Movim setup: .env file, databases init, cache reload";
-        wantedBy = [ "multi-user.target" ];
-        requiredBy = [ "${phpExecutionUnit}.service" ];
-        before = [ "${phpExecutionUnit}.service" ];
-        after = lib.optional cfg.database.createLocally dbService;
-        requires = lib.optional cfg.database.createLocally dbService;
-
-        serviceConfig = {
-          Type = "oneshot";
-          User = cfg.user;
-          Group = cfg.group;
-          UMask = "077";
-        } // lib.optionalAttrs (cfg.secretFile != null) {
-          LoadCredential = "env-secrets:${cfg.secretFile}";
-        };
-
-        script = ''
-          # Env vars
-          rm -f ${cfg.dataDir}/.env
-          cp --no-preserve=all ${configFile} ${cfg.dataDir}/.env
-          echo -e '\n' >> ${cfg.dataDir}/.env
-          if [[ -f "$CREDENTIALS_DIRECTORY/env-secrets"  ]]; then
-            cat "$CREDENTIALS_DIRECTORY/env-secrets" >> ${cfg.dataDir}/.env
-            echo -e '\n' >> ${cfg.dataDir}/.env
-          fi
-
-          # Caches, logs
-          mkdir -p ${cfg.dataDir}/public/cache ${cfg.logDir} ${cfg.runtimeDir}/cache
-          chmod -R ug+rw ${cfg.dataDir}/public/cache
-          chmod -R ug+rw ${cfg.logDir}
-          chmod -R ug+rwx ${cfg.runtimeDir}/cache
-
-          # Migrations
-          MOVIM_VERSION="${package.version}"
-          if [[ ! -f "${cfg.dataDir}/.migration-version" ]] || [[ "$MOVIM_VERSION" != "$(<${cfg.dataDir}/.migration-version)" ]]; then
-            ${package}/bin/movim-composer movim:migrate && echo $MOVIM_VERSION > ${cfg.dataDir}/.migration-version
-          fi
-        ''
-        + lib.optionalString (podConfigFlags != "") (
-          let
-            flags = lib.concatStringsSep " "
-              ([ "--no-interaction" ]
-                ++ lib.optional cfg.debug "-vvv"
-                ++ lib.optional (!cfg.debug && cfg.verbose) "-v");
-          in
-          ''
-            ${lib.getExe package} config ${podConfigFlags}
-          ''
-        );
-      };
-
-      services.movim = {
-        description = "Movim daemon";
-        wantedBy = [ "multi-user.target" ];
-        after = [ "movim-data-setup.service" ];
-        requires = [ "movim-data-setup.service" ]
-          ++ lib.optional cfg.database.createLocally dbService;
-        environment = {
-          PUBLIC_URL = "//${cfg.domain}";
-          WS_PORT = builtins.toString cfg.port;
-        };
-
-        serviceConfig = {
-          User = cfg.user;
-          Group = cfg.group;
-          WorkingDirectory = "${package}/share/php/movim";
-          ExecStart = "${lib.getExe package} start";
-        };
-      };
-
-      services.${phpExecutionUnit} = {
-        after = [ "movim-data-setup.service" ];
-        requires = [ "movim-data-setup.service" ]
-          ++ lib.optional cfg.database.createLocally dbService;
-      };
-
-      tmpfiles.settings."10-movim" = with cfg; {
-        "${dataDir}".d = { inherit user group; mode = "0710"; };
-        "${dataDir}/public".d = { inherit user group; mode = "0750"; };
-        "${dataDir}/public/cache".d = { inherit user group; mode = "0750"; };
-        "${runtimeDir}".d = { inherit user group; mode = "0700"; };
-        "${runtimeDir}/cache".d = { inherit user group; mode = "0700"; };
-        "${logDir}".d = { inherit user group; mode = "0700"; };
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix
deleted file mode 100644
index 7bcbde2a018e..000000000000
--- a/nixos/modules/services/web-apps/netbox.nix
+++ /dev/null
@@ -1,395 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.netbox;
-  pythonFmt = pkgs.formats.pythonVars {};
-  staticDir = cfg.dataDir + "/static";
-
-  settingsFile = pythonFmt.generate "netbox-settings.py" cfg.settings;
-  extraConfigFile = pkgs.writeTextFile {
-    name = "netbox-extraConfig.py";
-    text = cfg.extraConfig;
-  };
-  configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ];
-
-  pkg = (cfg.package.overrideAttrs (old: {
-    installPhase = old.installPhase + ''
-      ln -s ${configFile} $out/opt/netbox/netbox/netbox/configuration.py
-    '' + lib.optionalString cfg.enableLdap ''
-      ln -s ${cfg.ldapConfigPath} $out/opt/netbox/netbox/netbox/ldap_config.py
-    '';
-  })).override {
-    inherit (cfg) plugins;
-  };
-  netboxManageScript = with pkgs; (writeScriptBin "netbox-manage" ''
-    #!${stdenv.shell}
-    export PYTHONPATH=${pkg.pythonPath}
-    sudo -u netbox ${pkg}/bin/netbox "$@"
-  '');
-
-in {
-  options.services.netbox = {
-    enable = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = ''
-        Enable Netbox.
-
-        This module requires a reverse proxy that serves `/static` separately.
-        See this [example](https://github.com/netbox-community/netbox/blob/develop/contrib/nginx.conf/) on how to configure this.
-      '';
-    };
-
-    settings = lib.mkOption {
-      description = ''
-        Configuration options to set in `configuration.py`.
-        See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options.
-      '';
-
-      default = { };
-
-      type = lib.types.submodule {
-        freeformType = pythonFmt.type;
-
-        options = {
-          ALLOWED_HOSTS = lib.mkOption {
-            type = with lib.types; listOf str;
-            default = ["*"];
-            description = ''
-              A list of valid fully-qualified domain names (FQDNs) and/or IP
-              addresses that can be used to reach the NetBox service.
-            '';
-          };
-        };
-      };
-    };
-
-    listenAddress = lib.mkOption {
-      type = lib.types.str;
-      default = "[::1]";
-      description = ''
-        Address the server will listen on.
-      '';
-    };
-
-    package = lib.mkOption {
-      type = lib.types.package;
-      default =
-        if lib.versionAtLeast config.system.stateVersion "24.05"
-        then pkgs.netbox_3_7
-        else if lib.versionAtLeast config.system.stateVersion "23.11"
-        then pkgs.netbox_3_6
-        else if lib.versionAtLeast config.system.stateVersion "23.05"
-        then pkgs.netbox_3_5
-        else pkgs.netbox_3_3;
-      defaultText = lib.literalExpression ''
-        if lib.versionAtLeast config.system.stateVersion "24.05"
-        then pkgs.netbox_3_7
-        else if lib.versionAtLeast config.system.stateVersion "23.11"
-        then pkgs.netbox_3_6
-        else if lib.versionAtLeast config.system.stateVersion "23.05"
-        then pkgs.netbox_3_5
-        else pkgs.netbox_3_3;
-      '';
-      description = ''
-        NetBox package to use.
-      '';
-    };
-
-    port = lib.mkOption {
-      type = lib.types.port;
-      default = 8001;
-      description = ''
-        Port the server will listen on.
-      '';
-    };
-
-    plugins = lib.mkOption {
-      type = with lib.types; functionTo (listOf package);
-      default = _: [];
-      defaultText = lib.literalExpression ''
-        python3Packages: with python3Packages; [];
-      '';
-      description = ''
-        List of plugin packages to install.
-      '';
-    };
-
-    dataDir = lib.mkOption {
-      type = lib.types.str;
-      default = "/var/lib/netbox";
-      description = ''
-        Storage path of netbox.
-      '';
-    };
-
-    secretKeyFile = lib.mkOption {
-      type = lib.types.path;
-      description = ''
-        Path to a file containing the secret key.
-      '';
-    };
-
-    extraConfig = lib.mkOption {
-      type = lib.types.lines;
-      default = "";
-      description = ''
-        Additional lines of configuration appended to the `configuration.py`.
-        See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options.
-      '';
-    };
-
-    enableLdap = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = ''
-        Enable LDAP-Authentication for Netbox.
-
-        This requires a configuration file being pass through `ldapConfigPath`.
-      '';
-    };
-
-    ldapConfigPath = lib.mkOption {
-      type = lib.types.path;
-      default = "";
-      description = ''
-        Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`.
-        See the [documentation](https://netbox.readthedocs.io/en/stable/installation/6-ldap/#configuration) for possible options.
-      '';
-      example = ''
-        import ldap
-        from django_auth_ldap.config import LDAPSearch, PosixGroupType
-
-        AUTH_LDAP_SERVER_URI = "ldaps://ldap.example.com/"
-
-        AUTH_LDAP_USER_SEARCH = LDAPSearch(
-            "ou=accounts,ou=posix,dc=example,dc=com",
-            ldap.SCOPE_SUBTREE,
-            "(uid=%(user)s)",
-        )
-
-        AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
-            "ou=groups,ou=posix,dc=example,dc=com",
-            ldap.SCOPE_SUBTREE,
-            "(objectClass=posixGroup)",
-        )
-        AUTH_LDAP_GROUP_TYPE = PosixGroupType()
-
-        # Mirror LDAP group assignments.
-        AUTH_LDAP_MIRROR_GROUPS = True
-
-        # For more granular permissions, we can map LDAP groups to Django groups.
-        AUTH_LDAP_FIND_GROUP_PERMS = True
-      '';
-    };
-    keycloakClientSecret = lib.mkOption {
-      type = with lib.types; nullOr path;
-      default = null;
-      description = ''
-        File that contains the keycloak client secret.
-      '';
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    services.netbox = {
-      plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
-      settings = {
-        STATIC_ROOT = staticDir;
-        MEDIA_ROOT = "${cfg.dataDir}/media";
-        REPORTS_ROOT = "${cfg.dataDir}/reports";
-        SCRIPTS_ROOT = "${cfg.dataDir}/scripts";
-
-        GIT_PATH = "${pkgs.gitMinimal}/bin/git";
-
-        DATABASE = {
-          NAME = "netbox";
-          USER = "netbox";
-          HOST = "/run/postgresql";
-        };
-
-        # Redis database settings. Redis is used for caching and for queuing
-        # background tasks such as webhook events. A separate configuration
-        # exists for each. Full connection details are required in both
-        # sections, and it is strongly recommended to use two separate database
-        # IDs.
-        REDIS = {
-            tasks = {
-                URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=0";
-                SSL = false;
-            };
-            caching =  {
-                URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=1";
-                SSL = false;
-            };
-        };
-
-        REMOTE_AUTH_BACKEND = lib.mkIf cfg.enableLdap "netbox.authentication.LDAPBackend";
-
-        LOGGING = lib.mkDefault {
-          version = 1;
-
-          formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";
-
-          handlers.console = {
-            class = "logging.StreamHandler";
-            formatter = "precise";
-          };
-
-          # log to console/systemd instead of file
-          root = {
-            level = "INFO";
-            handlers = [ "console" ];
-          };
-        };
-      };
-
-      extraConfig = ''
-        with open("${cfg.secretKeyFile}", "r") as file:
-            SECRET_KEY = file.readline()
-      '' + (lib.optionalString (cfg.keycloakClientSecret != null) ''
-        with open("${cfg.keycloakClientSecret}", "r") as file:
-            SOCIAL_AUTH_KEYCLOAK_SECRET = file.readline()
-      '');
-    };
-
-    services.redis.servers.netbox.enable = true;
-
-    services.postgresql = {
-      enable = true;
-      ensureDatabases = [ "netbox" ];
-      ensureUsers = [
-        {
-          name = "netbox";
-          ensureDBOwnership = true;
-        }
-      ];
-    };
-
-    environment.systemPackages = [ netboxManageScript ];
-
-    systemd.targets.netbox = {
-      description = "Target for all NetBox services";
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network-online.target" "redis-netbox.service" ];
-    };
-
-    systemd.services = let
-      defaultServiceConfig = {
-        WorkingDirectory = "${cfg.dataDir}";
-        User = "netbox";
-        Group = "netbox";
-        StateDirectory = "netbox";
-        StateDirectoryMode = "0750";
-        Restart = "on-failure";
-        RestartSec = 30;
-      };
-    in {
-      netbox = {
-        description = "NetBox WSGI Service";
-        documentation = [ "https://docs.netbox.dev/" ];
-
-        wantedBy = [ "netbox.target" ];
-
-        after = [ "network-online.target" ];
-        wants = [ "network-online.target" ];
-
-        environment.PYTHONPATH = pkg.pythonPath;
-
-        preStart = ''
-          # On the first run, or on upgrade / downgrade, run migrations and related.
-          # This mostly correspond to upstream NetBox's 'upgrade.sh' script.
-          versionFile="${cfg.dataDir}/version"
-
-          if [[ -e "$versionFile" && "$(cat "$versionFile")" == "${cfg.package.version}" ]]; then
-            exit 0
-          fi
-
-          ${pkg}/bin/netbox migrate
-          ${pkg}/bin/netbox trace_paths --no-input
-          ${pkg}/bin/netbox collectstatic --no-input
-          ${pkg}/bin/netbox remove_stale_contenttypes --no-input
-          ${pkg}/bin/netbox reindex --lazy
-          ${pkg}/bin/netbox clearsessions
-          ${lib.optionalString
-            # The clearcache command was removed in 3.7.0:
-            # https://github.com/netbox-community/netbox/issues/14458
-            (lib.versionOlder cfg.package.version "3.7.0")
-            "${pkg}/bin/netbox clearcache"}
-
-          echo "${cfg.package.version}" > "$versionFile"
-        '';
-
-        serviceConfig = defaultServiceConfig // {
-          ExecStart = ''
-            ${pkg.gunicorn}/bin/gunicorn netbox.wsgi \
-              --bind ${cfg.listenAddress}:${toString cfg.port} \
-              --pythonpath ${pkg}/opt/netbox/netbox
-          '';
-          PrivateTmp = true;
-        };
-      };
-
-      netbox-rq = {
-        description = "NetBox Request Queue Worker";
-        documentation = [ "https://docs.netbox.dev/" ];
-
-        wantedBy = [ "netbox.target" ];
-        after = [ "netbox.service" ];
-
-        environment.PYTHONPATH = pkg.pythonPath;
-
-        serviceConfig = defaultServiceConfig // {
-          ExecStart = ''
-            ${pkg}/bin/netbox rqworker high default low
-          '';
-          PrivateTmp = true;
-        };
-      };
-
-      netbox-housekeeping = {
-        description = "NetBox housekeeping job";
-        documentation = [ "https://docs.netbox.dev/" ];
-
-        wantedBy = [ "multi-user.target" ];
-
-        after = [ "network-online.target" "netbox.service" ];
-        wants = [ "network-online.target" ];
-
-        environment.PYTHONPATH = pkg.pythonPath;
-
-        serviceConfig = defaultServiceConfig // {
-          Type = "oneshot";
-          ExecStart = ''
-            ${pkg}/bin/netbox housekeeping
-          '';
-        };
-      };
-    };
-
-    systemd.timers.netbox-housekeeping = {
-      description = "Run NetBox housekeeping job";
-      documentation = [ "https://docs.netbox.dev/" ];
-
-      wantedBy = [ "multi-user.target" ];
-
-      after = [ "network-online.target" "netbox.service" ];
-      wants = [ "network-online.target" ];
-
-      timerConfig = {
-        OnCalendar = "daily";
-        AccuracySec = "1h";
-        Persistent = true;
-      };
-    };
-
-    users.users.netbox = {
-      home = "${cfg.dataDir}";
-      isSystemUser = true;
-      group = "netbox";
-    };
-    users.groups.netbox = {};
-    users.groups."${config.services.redis.servers.netbox.user}".members = [ "netbox" ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/nextcloud-notify_push.nix b/nixos/modules/services/web-apps/nextcloud-notify_push.nix
deleted file mode 100644
index 4da5aff0c83e..000000000000
--- a/nixos/modules/services/web-apps/nextcloud-notify_push.nix
+++ /dev/null
@@ -1,124 +0,0 @@
-{ config, options, lib, pkgs, ... }:
-
-let
-  cfg = config.services.nextcloud.notify_push;
-  cfgN = config.services.nextcloud;
-in
-{
-  options.services.nextcloud.notify_push = {
-    enable = lib.mkEnableOption "Notify push";
-
-    package = lib.mkOption {
-      type = lib.types.package;
-      default = pkgs.nextcloud-notify_push;
-      defaultText = lib.literalMD "pkgs.nextcloud-notify_push";
-      description = "Which package to use for notify_push";
-    };
-
-    socketPath = lib.mkOption {
-      type = lib.types.str;
-      default = "/run/nextcloud-notify_push/sock";
-      description = "Socket path to use for notify_push";
-    };
-
-    logLevel = lib.mkOption {
-      type = lib.types.enum [ "error" "warn" "info" "debug" "trace" ];
-      default = "error";
-      description = "Log level";
-    };
-
-    bendDomainToLocalhost = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = ''
-        Whether to add an entry to `/etc/hosts` for the configured nextcloud domain to point to `localhost` and add `localhost `to nextcloud's `trusted_proxies` config option.
-
-        This is useful when nextcloud's domain is not a static IP address and when the reverse proxy cannot be bypassed because the backend connection is done via unix socket.
-      '';
-    };
-  } // (
-    lib.genAttrs [
-      "dbtype"
-      "dbname"
-      "dbuser"
-      "dbpassFile"
-      "dbhost"
-      "dbport"
-      "dbtableprefix"
-    ] (
-      opt: options.services.nextcloud.config.${opt} // {
-        default = config.services.nextcloud.config.${opt};
-        defaultText = "config.services.nextcloud.config.${opt}";
-      }
-    )
-  );
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.nextcloud-notify_push = let
-      nextcloudUrl = "http${lib.optionalString cfgN.https "s"}://${cfgN.hostName}";
-    in {
-      description = "Push daemon for Nextcloud clients";
-      documentation = [ "https://github.com/nextcloud/notify_push" ];
-      after = [
-        "phpfpm-nextcloud.service"
-        "redis-nextcloud.service"
-      ];
-      wantedBy = [ "multi-user.target" ];
-      environment = {
-        NEXTCLOUD_URL = nextcloudUrl;
-        SOCKET_PATH = cfg.socketPath;
-        DATABASE_PREFIX = cfg.dbtableprefix;
-        LOG = cfg.logLevel;
-      };
-      postStart = ''
-        ${cfgN.occ}/bin/nextcloud-occ notify_push:setup ${nextcloudUrl}/push
-      '';
-      script = let
-        dbType = if cfg.dbtype == "pgsql" then "postgresql" else cfg.dbtype;
-        dbUser = lib.optionalString (cfg.dbuser != null) cfg.dbuser;
-        dbPass = lib.optionalString (cfg.dbpassFile != null) ":$DATABASE_PASSWORD";
-        isSocket = lib.hasPrefix "/" (toString cfg.dbhost);
-        dbHost = lib.optionalString (cfg.dbhost != null) (if
-          isSocket then
-            if dbType == "postgresql" then "?host=${cfg.dbhost}" else
-            if dbType == "mysql" then "?socket=${cfg.dbhost}" else throw "unsupported dbtype"
-          else
-            "@${cfg.dbhost}");
-        dbName = lib.optionalString (cfg.dbname != null) "/${cfg.dbname}";
-        dbUrl = "${dbType}://${dbUser}${dbPass}${lib.optionalString (!isSocket) dbHost}${dbName}${lib.optionalString isSocket dbHost}";
-      in lib.optionalString (dbPass != "") ''
-        export DATABASE_PASSWORD="$(<"${cfg.dbpassFile}")"
-      '' + ''
-        export DATABASE_URL="${dbUrl}"
-        exec ${cfg.package}/bin/notify_push '${cfgN.datadir}/config/config.php'
-      '';
-      serviceConfig = {
-        User = "nextcloud";
-        Group = "nextcloud";
-        RuntimeDirectory = [ "nextcloud-notify_push" ];
-        Restart = "on-failure";
-        RestartSec = "5s";
-        Type = "notify";
-      };
-    };
-
-    networking.hosts = lib.mkIf cfg.bendDomainToLocalhost {
-      "127.0.0.1" = [ cfgN.hostName ];
-      "::1" = [ cfgN.hostName ];
-    };
-
-    services = lib.mkMerge [
-      {
-        nginx.virtualHosts.${cfgN.hostName}.locations."^~ /push/" = {
-          proxyPass = "http://unix:${cfg.socketPath}";
-          proxyWebsockets = true;
-          recommendedProxySettings = true;
-        };
-      }
-
-      (lib.mkIf cfg.bendDomainToLocalhost {
-        nextcloud.settings.trusted_proxies = [ "127.0.0.1" "::1" ];
-      })
-    ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md
deleted file mode 100644
index 0b615deae44b..000000000000
--- a/nixos/modules/services/web-apps/nextcloud.md
+++ /dev/null
@@ -1,242 +0,0 @@
-# Nextcloud {#module-services-nextcloud}
-
-[Nextcloud](https://nextcloud.com/) is an open-source,
-self-hostable cloud platform. The server setup can be automated using
-[services.nextcloud](#opt-services.nextcloud.enable). A
-desktop client is packaged at `pkgs.nextcloud-client`.
-
-The current default by NixOS is `nextcloud29` which is also the latest
-major version available.
-
-## Basic usage {#module-services-nextcloud-basic-usage}
-
-Nextcloud is a PHP-based application which requires an HTTP server
-([`services.nextcloud`](#opt-services.nextcloud.enable)
-and optionally supports
-[`services.nginx`](#opt-services.nginx.enable)).
-
-For the database, you can set
-[`services.nextcloud.config.dbtype`](#opt-services.nextcloud.config.dbtype) to
-either `sqlite` (the default), `mysql`, or `pgsql`. The simplest is `sqlite`,
-which will be automatically created and managed by the application. For the
-last two, you can easily create a local database by setting
-[`services.nextcloud.database.createLocally`](#opt-services.nextcloud.database.createLocally)
-to `true`, Nextcloud will automatically be configured to connect to it through
-socket.
-
-A very basic configuration may look like this:
-```nix
-{ pkgs, ... }:
-{
-  services.nextcloud = {
-    enable = true;
-    hostName = "nextcloud.tld";
-    database.createLocally = true;
-    config = {
-      dbtype = "pgsql";
-      adminpassFile = "/path/to/admin-pass-file";
-    };
-  };
-
-  networking.firewall.allowedTCPPorts = [ 80 443 ];
-}
-```
-
-The `hostName` option is used internally to configure an HTTP
-server using [`PHP-FPM`](https://php-fpm.org/)
-and `nginx`. The `config` attribute set is
-used by the imperative installer and all values are written to an additional file
-to ensure that changes can be applied by changing the module's options.
-
-In case the application serves multiple domains (those are checked with
-[`$_SERVER['HTTP_HOST']`](https://www.php.net/manual/en/reserved.variables.server.php))
-it's needed to add them to
-[`services.nextcloud.settings.trusted_domains`](#opt-services.nextcloud.settings.trusted_domains).
-
-Auto updates for Nextcloud apps can be enabled using
-[`services.nextcloud.autoUpdateApps`](#opt-services.nextcloud.autoUpdateApps.enable).
-
-## Common problems {#module-services-nextcloud-pitfalls-during-upgrade}
-
-  - **General notes.**
-    Unfortunately Nextcloud appears to be very stateful when it comes to
-    managing its own configuration. The config file lives in the home directory
-    of the `nextcloud` user (by default
-    `/var/lib/nextcloud/config/config.php`) and is also used to
-    track several states of the application (e.g., whether installed or not).
-
-     All configuration parameters are also stored in
-    {file}`/var/lib/nextcloud/config/override.config.php` which is generated by
-    the module and linked from the store to ensure that all values from
-    {file}`config.php` can be modified by the module.
-    However {file}`config.php` manages the application's state and shouldn't be
-    touched manually because of that.
-
-    ::: {.warning}
-    Don't delete {file}`config.php`! This file
-    tracks the application's state and a deletion can cause unwanted
-    side-effects!
-    :::
-
-    ::: {.warning}
-    Don't rerun `nextcloud-occ maintenance:install`!
-    This command tries to install the application
-    and can cause unwanted side-effects!
-    :::
-  - **Multiple version upgrades.**
-    Nextcloud doesn't allow to move more than one major-version forward. E.g., if you're on
-    `v16`, you cannot upgrade to `v18`, you need to upgrade to
-    `v17` first. This is ensured automatically as long as the
-    [stateVersion](#opt-system.stateVersion) is declared properly. In that case
-    the oldest version available (one major behind the one from the previous NixOS
-    release) will be selected by default and the module will generate a warning that reminds
-    the user to upgrade to latest Nextcloud *after* that deploy.
-  - **`Error: Command "upgrade" is not defined.`**
-    This error usually occurs if the initial installation
-    ({command}`nextcloud-occ maintenance:install`) has failed. After that, the application
-    is not installed, but the upgrade is attempted to be executed. Further context can
-    be found in [NixOS/nixpkgs#111175](https://github.com/NixOS/nixpkgs/issues/111175).
-
-    First of all, it makes sense to find out what went wrong by looking at the logs
-    of the installation via {command}`journalctl -u nextcloud-setup` and try to fix
-    the underlying issue.
-
-    - If this occurs on an *existing* setup, this is most likely because
-      the maintenance mode is active. It can be deactivated by running
-      {command}`nextcloud-occ maintenance:mode --off`. It's advisable though to
-      check the logs first on why the maintenance mode was activated.
-    - ::: {.warning}
-      Only perform the following measures on
-      *freshly installed instances!*
-      :::
-
-      A re-run of the installer can be forced by *deleting*
-      {file}`/var/lib/nextcloud/config/config.php`. This is the only time
-      advisable because the fresh install doesn't have any state that can be lost.
-      In case that doesn't help, an entire re-creation can be forced via
-      {command}`rm -rf ~nextcloud/`.
-
-  - **Server-side encryption.**
-    Nextcloud supports [server-side encryption (SSE)](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html).
-    This is not an end-to-end encryption, but can be used to encrypt files that will be persisted
-    to external storage such as S3.
-
-## Using an alternative webserver as reverse-proxy (e.g. `httpd`) {#module-services-nextcloud-httpd}
-
-By default, `nginx` is used as reverse-proxy for `nextcloud`.
-However, it's possible to use e.g. `httpd` by explicitly disabling
-`nginx` using [](#opt-services.nginx.enable) and fixing the
-settings `listen.owner` &amp; `listen.group` in the
-[corresponding `phpfpm` pool](#opt-services.phpfpm.pools).
-
-An exemplary configuration may look like this:
-```nix
-{ config, lib, pkgs, ... }: {
-  services.nginx.enable = false;
-  services.nextcloud = {
-    enable = true;
-    hostName = "localhost";
-
-    /* further, required options */
-  };
-  services.phpfpm.pools.nextcloud.settings = {
-    "listen.owner" = config.services.httpd.user;
-    "listen.group" = config.services.httpd.group;
-  };
-  services.httpd = {
-    enable = true;
-    adminAddr = "webmaster@localhost";
-    extraModules = [ "proxy_fcgi" ];
-    virtualHosts."localhost" = {
-      documentRoot = config.services.nextcloud.package;
-      extraConfig = ''
-        <Directory "${config.services.nextcloud.package}">
-          <FilesMatch "\.php$">
-            <If "-f %{REQUEST_FILENAME}">
-              SetHandler "proxy:unix:${config.services.phpfpm.pools.nextcloud.socket}|fcgi://localhost/"
-            </If>
-          </FilesMatch>
-          <IfModule mod_rewrite.c>
-            RewriteEngine On
-            RewriteBase /
-            RewriteRule ^index\.php$ - [L]
-            RewriteCond %{REQUEST_FILENAME} !-f
-            RewriteCond %{REQUEST_FILENAME} !-d
-            RewriteRule . /index.php [L]
-          </IfModule>
-          DirectoryIndex index.php
-          Require all granted
-          Options +FollowSymLinks
-        </Directory>
-      '';
-    };
-  };
-}
-```
-
-## Installing Apps and PHP extensions {#installing-apps-php-extensions-nextcloud}
-
-Nextcloud apps are installed statefully through the web interface.
-Some apps may require extra PHP extensions to be installed.
-This can be configured with the [](#opt-services.nextcloud.phpExtraExtensions) setting.
-
-Alternatively, extra apps can also be declared with the [](#opt-services.nextcloud.extraApps) setting.
-When using this setting, apps can no longer be managed statefully because this can lead to Nextcloud updating apps
-that are managed by Nix. If you want automatic updates it is recommended that you use web interface to install apps.
-
-## Known warnings {#module-services-nextcloud-known-warnings}
-
-### Failed to get an iterator for log entries: Logreader application only supports "file" log_type {#module-services-nextcloud-warning-logreader}
-
-This is because
-
-* our module writes logs into the journal (`journalctl -t Nextcloud`)
-* the Logreader application that allows reading logs in the admin panel is enabled
-  by default and requires logs written to a file.
-
-The logreader application doesn't work, as it was the case before. The only change is that
-it complains loudly now. So nothing actionable here by default. Alternatively you can
-
-* disable the logreader application to shut up the "error".
-
-  We can't really do that by default since whether apps are enabled/disabled is part
-  of the application's state and tracked inside the database.
-
-* set [](#opt-services.nextcloud.settings.log_type) to "file" to be able to view logs
-  from the admin panel.
-
-## Maintainer information {#module-services-nextcloud-maintainer-info}
-
-As stated in the previous paragraph, we must provide a clean upgrade-path for Nextcloud
-since it cannot move more than one major version forward on a single upgrade. This chapter
-adds some notes how Nextcloud updates should be rolled out in the future.
-
-While minor and patch-level updates are no problem and can be done directly in the
-package-expression (and should be backported to supported stable branches after that),
-major-releases should be added in a new attribute (e.g. Nextcloud `v19.0.0`
-should be available in `nixpkgs` as `pkgs.nextcloud19`).
-To provide simple upgrade paths it's generally useful to backport those as well to stable
-branches. As long as the package-default isn't altered, this won't break existing setups.
-After that, the versioning-warning in the `nextcloud`-module should be
-updated to make sure that the
-[package](#opt-services.nextcloud.package)-option selects the latest version
-on fresh setups.
-
-If major-releases will be abandoned by upstream, we should check first if those are needed
-in NixOS for a safe upgrade-path before removing those. In that case we should keep those
-packages, but mark them as insecure in an expression like this (in
-`<nixpkgs/pkgs/servers/nextcloud/default.nix>`):
-```nix
-/* ... */
-{
-  nextcloud17 = generic {
-    version = "17.0.x";
-    sha256 = "0000000000000000000000000000000000000000000000000000";
-    eol = true;
-  };
-}
-```
-
-Ideally we should make sure that it's possible to jump two NixOS versions forward:
-i.e. the warnings and the logic in the module should guard a user to upgrade from a
-Nextcloud on e.g. 19.09 to a Nextcloud on 20.09.
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
deleted file mode 100644
index d7eb2c6cb734..000000000000
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ /dev/null
@@ -1,1244 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.nextcloud;
-  fpm = config.services.phpfpm.pools.nextcloud;
-
-  jsonFormat = pkgs.formats.json {};
-
-  defaultPHPSettings = {
-    output_buffering = "0";
-    short_open_tag = "Off";
-    expose_php = "Off";
-    error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
-    display_errors = "stderr";
-    "opcache.interned_strings_buffer" = "8";
-    "opcache.max_accelerated_files" = "10000";
-    "opcache.memory_consumption" = "128";
-    "opcache.revalidate_freq" = "1";
-    "opcache.fast_shutdown" = "1";
-    "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
-    catch_workers_output = "yes";
-  };
-
-  appStores = {
-    # default apps bundled with pkgs.nextcloudXX, e.g. files, contacts
-    apps = {
-      enabled = true;
-      writable = false;
-    };
-    # apps installed via cfg.extraApps
-    nix-apps = {
-      enabled = cfg.extraApps != { };
-      linkTarget = pkgs.linkFarm "nix-apps"
-        (mapAttrsToList (name: path: { inherit name path; }) cfg.extraApps);
-      writable = false;
-    };
-    # apps installed via the app store.
-    store-apps = {
-      enabled = cfg.appstoreEnable == null || cfg.appstoreEnable;
-      linkTarget = "${cfg.home}/store-apps";
-      writable = true;
-    };
-  };
-
-  webroot = pkgs.runCommandLocal
-    "${cfg.package.name or "nextcloud"}-with-apps"
-    { }
-    ''
-      mkdir $out
-      ln -sfv "${cfg.package}"/* "$out"
-      ${concatStrings
-        (mapAttrsToList (name: store: optionalString (store.enabled && store?linkTarget) ''
-          if [ -e "$out"/${name} ]; then
-            echo "Didn't expect ${name} already in $out!"
-            exit 1
-          fi
-          ln -sfTv ${store.linkTarget} "$out"/${name}
-        '') appStores)}
-    '';
-
-  inherit (cfg) datadir;
-
-  phpPackage = cfg.phpPackage.buildEnv {
-    extensions = { enabled, all }:
-      (with all; enabled
-        ++ [ bz2 intl sodium ] # recommended
-        ++ optional cfg.enableImagemagick imagick
-        # Optionally enabled depending on caching settings
-        ++ optional cfg.caching.apcu apcu
-        ++ optional cfg.caching.redis redis
-        ++ optional cfg.caching.memcached memcached
-      )
-      ++ cfg.phpExtraExtensions all; # Enabled by user
-    extraConfig = toKeyValue cfg.phpOptions;
-  };
-
-  toKeyValue = generators.toKeyValue {
-    mkKeyValue = generators.mkKeyValueDefault {} " = ";
-  };
-
-  occ = pkgs.writeScriptBin "nextcloud-occ" ''
-    #! ${pkgs.runtimeShell}
-    cd ${webroot}
-    sudo=exec
-    if [[ "$USER" != nextcloud ]]; then
-      sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR --preserve-env=OC_PASS'
-    fi
-    export NEXTCLOUD_CONFIG_DIR="${datadir}/config"
-    $sudo \
-      ${phpPackage}/bin/php \
-      occ "$@"
-  '';
-
-  inherit (config.system) stateVersion;
-
-  mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
-  pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
-
-  nextcloudGreaterOrEqualThan = versionAtLeast cfg.package.version;
-  nextcloudOlderThan = versionOlder cfg.package.version;
-
-  # https://github.com/nextcloud/documentation/pull/11179
-  ocmProviderIsNotAStaticDirAnymore = nextcloudGreaterOrEqualThan "27.1.2"
-    || (nextcloudOlderThan "27.0.0" && nextcloudGreaterOrEqualThan "26.0.8");
-
-  overrideConfig = let
-    c = cfg.config;
-    requiresReadSecretFunction = c.dbpassFile != null || c.objectstore.s3.enable;
-    objectstoreConfig = let s3 = c.objectstore.s3; in optionalString s3.enable ''
-      'objectstore' => [
-        'class' => '\\OC\\Files\\ObjectStore\\S3',
-        'arguments' => [
-          'bucket' => '${s3.bucket}',
-          'autocreate' => ${boolToString s3.autocreate},
-          'key' => '${s3.key}',
-          'secret' => nix_read_secret('${s3.secretFile}'),
-          ${optionalString (s3.hostname != null) "'hostname' => '${s3.hostname}',"}
-          ${optionalString (s3.port != null) "'port' => ${toString s3.port},"}
-          'use_ssl' => ${boolToString s3.useSsl},
-          ${optionalString (s3.region != null) "'region' => '${s3.region}',"}
-          'use_path_style' => ${boolToString s3.usePathStyle},
-          ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"}
-        ],
-      ]
-    '';
-    showAppStoreSetting = cfg.appstoreEnable != null || cfg.extraApps != {};
-    renderedAppStoreSetting =
-      let
-        x = cfg.appstoreEnable;
-      in
-        if x == null then "false"
-        else boolToString x;
-    mkAppStoreConfig = name: { enabled, writable, ... }: optionalString enabled ''
-      [ 'path' => '${webroot}/${name}', 'url' => '/${name}', 'writable' => ${boolToString writable} ],
-    '';
-  in pkgs.writeText "nextcloud-config.php" ''
-    <?php
-    ${optionalString requiresReadSecretFunction ''
-      function nix_read_secret($file) {
-        if (!file_exists($file)) {
-          throw new \RuntimeException(sprintf(
-            "Cannot start Nextcloud, secret file %s set by NixOS doesn't seem to "
-            . "exist! Please make sure that the file exists and has appropriate "
-            . "permissions for user & group 'nextcloud'!",
-            $file
-          ));
-        }
-        return trim(file_get_contents($file));
-      }''}
-    function nix_decode_json_file($file, $error) {
-      if (!file_exists($file)) {
-        throw new \RuntimeException(sprintf($error, $file));
-      }
-      $decoded = json_decode(file_get_contents($file), true);
-
-      if (json_last_error() !== JSON_ERROR_NONE) {
-        throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg()));
-      }
-
-      return $decoded;
-    }
-    $CONFIG = [
-      'apps_paths' => [
-        ${concatStrings (mapAttrsToList mkAppStoreConfig appStores)}
-      ],
-      ${optionalString (showAppStoreSetting) "'appstoreenabled' => ${renderedAppStoreSetting},"}
-      ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
-      ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
-      ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"}
-      ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
-      ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
-      ${optionalString (c.dbpassFile != null) ''
-          'dbpassword' => nix_read_secret(
-            "${c.dbpassFile}"
-          ),
-        ''
-      }
-      'dbtype' => '${c.dbtype}',
-      ${objectstoreConfig}
-    ];
-
-    $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
-      "${jsonFormat.generate "nextcloud-settings.json" cfg.settings}",
-      "impossible: this should never happen (decoding generated settings file %s failed)"
-    ));
-
-    ${optionalString (cfg.secretFile != null) ''
-      $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
-        "${cfg.secretFile}",
-        "Cannot start Nextcloud, secrets file %s set by NixOS doesn't exist!"
-      ));
-    ''}
-  '';
-in {
-
-  imports = [
-    (mkRemovedOptionModule [ "services" "nextcloud" "enableBrokenCiphersForSSE" ] ''
-      This option has no effect since there's no supported Nextcloud version packaged here
-      using OpenSSL for RC4 SSE.
-    '')
-    (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbport" ] ''
-      Add port to services.nextcloud.config.dbhost instead.
-    '')
-    (mkRenamedOptionModule
-      [ "services" "nextcloud" "logLevel" ] [ "services" "nextcloud" "settings" "loglevel" ])
-    (mkRenamedOptionModule
-      [ "services" "nextcloud" "logType" ] [ "services" "nextcloud" "settings" "log_type" ])
-    (mkRenamedOptionModule
-      [ "services" "nextcloud" "config" "defaultPhoneRegion" ] [ "services" "nextcloud" "settings" "default_phone_region" ])
-    (mkRenamedOptionModule
-      [ "services" "nextcloud" "config" "overwriteProtocol" ] [ "services" "nextcloud" "settings" "overwriteprotocol" ])
-    (mkRenamedOptionModule
-      [ "services" "nextcloud" "skeletonDirectory" ] [ "services" "nextcloud" "settings" "skeletondirectory" ])
-    (mkRenamedOptionModule
-      [ "services" "nextcloud" "globalProfiles" ] [ "services" "nextcloud" "settings" "profile.enabled" ])
-    (mkRenamedOptionModule
-      [ "services" "nextcloud" "config" "extraTrustedDomains" ] [ "services" "nextcloud" "settings" "trusted_domains" ])
-    (mkRenamedOptionModule
-      [ "services" "nextcloud" "config" "trustedProxies" ] [ "services" "nextcloud" "settings" "trusted_proxies" ])
-    (mkRenamedOptionModule ["services" "nextcloud" "extraOptions" ] [ "services" "nextcloud" "settings" ])
-  ];
-
-  options.services.nextcloud = {
-    enable = mkEnableOption "nextcloud";
-
-    hostName = mkOption {
-      type = types.str;
-      description = "FQDN for the nextcloud instance.";
-    };
-    home = mkOption {
-      type = types.str;
-      default = "/var/lib/nextcloud";
-      description = "Storage path of nextcloud.";
-    };
-    datadir = mkOption {
-      type = types.str;
-      default = config.services.nextcloud.home;
-      defaultText = literalExpression "config.services.nextcloud.home";
-      description = ''
-        Nextcloud's data storage path.  Will be [](#opt-services.nextcloud.home) by default.
-        This folder will be populated with a config.php file and a data folder which contains the state of the instance (excluding the database).";
-      '';
-      example = "/mnt/nextcloud-file";
-    };
-    extraApps = mkOption {
-      type = types.attrsOf types.package;
-      default = { };
-      description = ''
-        Extra apps to install. Should be an attrSet of appid to packages generated by fetchNextcloudApp.
-        The appid must be identical to the "id" value in the apps appinfo/info.xml.
-        Using this will disable the appstore to prevent Nextcloud from updating these apps (see [](#opt-services.nextcloud.appstoreEnable)).
-      '';
-      example = literalExpression ''
-        {
-          inherit (pkgs.nextcloud25Packages.apps) mail calendar contact;
-          phonetrack = pkgs.fetchNextcloudApp {
-            name = "phonetrack";
-            sha256 = "0qf366vbahyl27p9mshfma1as4nvql6w75zy2zk5xwwbp343vsbc";
-            url = "https://gitlab.com/eneiluj/phonetrack-oc/-/wikis/uploads/931aaaf8dca24bf31a7e169a83c17235/phonetrack-0.6.9.tar.gz";
-            version = "0.6.9";
-          };
-        }
-        '';
-    };
-    extraAppsEnable = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Automatically enable the apps in [](#opt-services.nextcloud.extraApps) every time Nextcloud starts.
-        If set to false, apps need to be enabled in the Nextcloud web user interface or with `nextcloud-occ app:enable`.
-      '';
-    };
-    appstoreEnable = mkOption {
-      type = types.nullOr types.bool;
-      default = null;
-      example = true;
-      description = ''
-        Allow the installation and updating of apps from the Nextcloud appstore.
-        Enabled by default unless there are packages in [](#opt-services.nextcloud.extraApps).
-        Set this to true to force enable the store even if [](#opt-services.nextcloud.extraApps) is used.
-        Set this to false to disable the installation of apps from the global appstore. App management is always enabled regardless of this setting.
-      '';
-    };
-    https = mkOption {
-      type = types.bool;
-      default = false;
-      description = "Use HTTPS for generated links.";
-    };
-    package = mkOption {
-      type = types.package;
-      description = "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud26" "nextcloud27" "nextcloud28" ];
-    };
-    phpPackage = mkPackageOption pkgs "php" {
-      example = "php82";
-    };
-
-    maxUploadSize = mkOption {
-      default = "512M";
-      type = types.str;
-      description = ''
-        The upload limit for files. This changes the relevant options
-        in php.ini and nginx if enabled.
-      '';
-    };
-
-    webfinger = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Enable this option if you plan on using the webfinger plugin.
-        The appropriate nginx rewrite rules will be added to your configuration.
-      '';
-    };
-
-    phpExtraExtensions = mkOption {
-      type = with types; functionTo (listOf package);
-      default = all: [];
-      defaultText = literalExpression "all: []";
-      description = ''
-        Additional PHP extensions to use for Nextcloud.
-        By default, only extensions necessary for a vanilla Nextcloud installation are enabled,
-        but you may choose from the list of available extensions and add further ones.
-        This is sometimes necessary to be able to install a certain Nextcloud app that has additional requirements.
-      '';
-      example = literalExpression ''
-        all: [ all.pdlib all.bz2 ]
-      '';
-    };
-
-    phpOptions = mkOption {
-      type = with types; attrsOf (oneOf [ str int ]);
-      defaultText = literalExpression (generators.toPretty { } defaultPHPSettings);
-      description = ''
-        Options for PHP's php.ini file for nextcloud.
-
-        Please note that this option is _additive_ on purpose while the
-        attribute values inside the default are option defaults: that means that
-
-        ```nix
-        {
-          services.nextcloud.phpOptions."opcache.interned_strings_buffer" = "23";
-        }
-        ```
-
-        will override the `php.ini` option `opcache.interned_strings_buffer` without
-        discarding the rest of the defaults.
-
-        Overriding all of `phpOptions` (including `upload_max_filesize`, `post_max_size`
-        and `memory_limit` which all point to [](#opt-services.nextcloud.maxUploadSize)
-        by default) can be done like this:
-
-        ```nix
-        {
-          services.nextcloud.phpOptions = lib.mkForce {
-            /* ... */
-          };
-        }
-        ```
-      '';
-    };
-
-    poolSettings = mkOption {
-      type = with types; attrsOf (oneOf [ str int bool ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = "32";
-        "pm.start_servers" = "2";
-        "pm.min_spare_servers" = "2";
-        "pm.max_spare_servers" = "4";
-        "pm.max_requests" = "500";
-      };
-      description = ''
-        Options for nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
-      '';
-    };
-
-    poolConfig = mkOption {
-      type = types.nullOr types.lines;
-      default = null;
-      description = ''
-        Options for Nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
-      '';
-    };
-
-    fastcgiTimeout = mkOption {
-      type = types.int;
-      default = 120;
-      description = ''
-        FastCGI timeout for database connection in seconds.
-      '';
-    };
-
-    database = {
-
-      createLocally = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to create the database and database user locally.
-        '';
-      };
-
-    };
-
-    config = {
-      dbtype = mkOption {
-        type = types.enum [ "sqlite" "pgsql" "mysql" ];
-        default = "sqlite";
-        description = "Database type.";
-      };
-      dbname = mkOption {
-        type = types.nullOr types.str;
-        default = "nextcloud";
-        description = "Database name.";
-      };
-      dbuser = mkOption {
-        type = types.nullOr types.str;
-        default = "nextcloud";
-        description = "Database user.";
-      };
-      dbpassFile = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          The full path to a file that contains the database password.
-        '';
-      };
-      dbhost = mkOption {
-        type = types.nullOr types.str;
-        default =
-          if pgsqlLocal then "/run/postgresql"
-          else if mysqlLocal then "localhost:/run/mysqld/mysqld.sock"
-          else "localhost";
-        defaultText = "localhost";
-        example = "localhost:5000";
-        description = ''
-          Database host (+port) or socket path.
-          If [](#opt-services.nextcloud.database.createLocally) is true and
-          [](#opt-services.nextcloud.config.dbtype) is either `pgsql` or `mysql`,
-          defaults to the correct Unix socket instead.
-        '';
-      };
-      dbtableprefix = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = "Table prefix in Nextcloud's database.";
-      };
-      adminuser = mkOption {
-        type = types.str;
-        default = "root";
-        description = ''
-          Username for the admin account. The username is only set during the
-          initial setup of Nextcloud! Since the username also acts as unique
-          ID internally, it cannot be changed later!
-        '';
-      };
-      adminpassFile = mkOption {
-        type = types.str;
-        description = ''
-          The full path to a file that contains the admin's password. Must be
-          readable by user `nextcloud`. The password is set only in the initial
-          setup of Nextcloud by the systemd service `nextcloud-setup.service`.
-        '';
-      };
-      objectstore = {
-        s3 = {
-          enable = mkEnableOption ''
-            S3 object storage as primary storage.
-
-            This mounts a bucket on an Amazon S3 object storage or compatible
-            implementation into the virtual filesystem.
-
-            Further details about this feature can be found in the
-            [upstream documentation](https://docs.nextcloud.com/server/22/admin_manual/configuration_files/primary_storage.html).
-          '';
-          bucket = mkOption {
-            type = types.str;
-            example = "nextcloud";
-            description = ''
-              The name of the S3 bucket.
-            '';
-          };
-          autocreate = mkOption {
-            type = types.bool;
-            description = ''
-              Create the objectstore if it does not exist.
-            '';
-          };
-          key = mkOption {
-            type = types.str;
-            example = "EJ39ITYZEUH5BGWDRUFY";
-            description = ''
-              The access key for the S3 bucket.
-            '';
-          };
-          secretFile = mkOption {
-            type = types.str;
-            example = "/var/nextcloud-objectstore-s3-secret";
-            description = ''
-              The full path to a file that contains the access secret. Must be
-              readable by user `nextcloud`.
-            '';
-          };
-          hostname = mkOption {
-            type = types.nullOr types.str;
-            default = null;
-            example = "example.com";
-            description = ''
-              Required for some non-Amazon implementations.
-            '';
-          };
-          port = mkOption {
-            type = types.nullOr types.port;
-            default = null;
-            description = ''
-              Required for some non-Amazon implementations.
-            '';
-          };
-          useSsl = mkOption {
-            type = types.bool;
-            default = true;
-            description = ''
-              Use SSL for objectstore access.
-            '';
-          };
-          region = mkOption {
-            type = types.nullOr types.str;
-            default = null;
-            example = "REGION";
-            description = ''
-              Required for some non-Amazon implementations.
-            '';
-          };
-          usePathStyle = mkOption {
-            type = types.bool;
-            default = false;
-            description = ''
-              Required for some non-Amazon S3 implementations.
-
-              Ordinarily, requests will be made with
-              `http://bucket.hostname.domain/`, but with path style
-              enabled requests are made with
-              `http://hostname.domain/bucket` instead.
-            '';
-          };
-          sseCKeyFile = mkOption {
-            type = types.nullOr types.path;
-            default = null;
-            example = "/var/nextcloud-objectstore-s3-sse-c-key";
-            description = ''
-              If provided this is the full path to a file that contains the key
-              to enable [server-side encryption with customer-provided keys][1]
-              (SSE-C).
-
-              The file must contain a random 32-byte key encoded as a base64
-              string, e.g. generated with the command
-
-              ```
-              openssl rand 32 | base64
-              ```
-
-              Must be readable by user `nextcloud`.
-
-              [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html
-            '';
-          };
-        };
-      };
-    };
-
-    enableImagemagick = mkEnableOption ''
-        the ImageMagick module for PHP.
-        This is used by the theming app and for generating previews of certain images (e.g. SVG and HEIF).
-        You may want to disable it for increased security. In that case, previews will still be available
-        for some images (e.g. JPEG and PNG).
-        See <https://github.com/nextcloud/server/issues/13099>.
-    '' // {
-      default = true;
-    };
-
-    configureRedis = lib.mkOption {
-      type = lib.types.bool;
-      default = config.services.nextcloud.notify_push.enable;
-      defaultText = literalExpression "config.services.nextcloud.notify_push.enable";
-      description = ''
-        Whether to configure Nextcloud to use the recommended Redis settings for small instances.
-
-        ::: {.note}
-        The `notify_push` app requires Redis to be configured. If this option is turned off, this must be configured manually.
-        :::
-      '';
-    };
-
-    caching = {
-      apcu = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether to load the APCu module into PHP.
-        '';
-      };
-      redis = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to load the Redis module into PHP.
-          You still need to enable Redis in your config.php.
-          See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
-        '';
-      };
-      memcached = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to load the Memcached module into PHP.
-          You still need to enable Memcached in your config.php.
-          See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
-        '';
-      };
-    };
-    autoUpdateApps = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Run a regular auto-update of all apps installed from the Nextcloud app store.
-        '';
-      };
-      startAt = mkOption {
-        type = with types; either str (listOf str);
-        default = "05:00:00";
-        example = "Sun 14:00:00";
-        description = ''
-          When to run the update. See `systemd.services.<name>.startAt`.
-        '';
-      };
-    };
-    occ = mkOption {
-      type = types.package;
-      default = occ;
-      defaultText = literalMD "generated script";
-      internal = true;
-      description = ''
-        The nextcloud-occ program preconfigured to target this Nextcloud instance.
-      '';
-    };
-
-    settings = mkOption {
-      type = types.submodule {
-        freeformType = jsonFormat.type;
-        options = {
-
-          loglevel = mkOption {
-            type = types.ints.between 0 4;
-            default = 2;
-            description = ''
-              Log level value between 0 (DEBUG) and 4 (FATAL).
-
-              - 0 (debug): Log all activity.
-
-              - 1 (info): Log activity such as user logins and file activities, plus warnings, errors, and fatal errors.
-
-              - 2 (warn): Log successful operations, as well as warnings of potential problems, errors and fatal errors.
-
-              - 3 (error): Log failed operations and fatal errors.
-
-              - 4 (fatal): Log only fatal errors that cause the server to stop.
-            '';
-          };
-          log_type = mkOption {
-            type = types.enum [ "errorlog" "file" "syslog" "systemd" ];
-            default = "syslog";
-            description = ''
-              Logging backend to use.
-              systemd requires the php-systemd package to be added to services.nextcloud.phpExtraExtensions.
-              See the [nextcloud documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/logging_configuration.html) for details.
-            '';
-          };
-          skeletondirectory = mkOption {
-            default = "";
-            type = types.str;
-            description = ''
-              The directory where the skeleton files are located. These files will be
-              copied to the data directory of new users. Leave empty to not copy any
-              skeleton files.
-            '';
-          };
-          trusted_domains = mkOption {
-            type = types.listOf types.str;
-            default = [];
-            description = ''
-              Trusted domains, from which the nextcloud installation will be
-              accessible. You don't need to add
-              `services.nextcloud.hostname` here.
-            '';
-          };
-          trusted_proxies = mkOption {
-            type = types.listOf types.str;
-            default = [];
-            description = ''
-              Trusted proxies, to provide if the nextcloud installation is being
-              proxied to secure against e.g. spoofing.
-            '';
-          };
-          overwriteprotocol = mkOption {
-            type = types.enum [ "" "http" "https" ];
-            default = "";
-            example = "https";
-            description = ''
-              Force Nextcloud to always use HTTP or HTTPS i.e. for link generation.
-              Nextcloud uses the currently used protocol by default, but when
-              behind a reverse-proxy, it may use `http` for everything although
-              Nextcloud may be served via HTTPS.
-            '';
-          };
-          default_phone_region = mkOption {
-            default = "";
-            type = types.str;
-            example = "DE";
-            description = ''
-              An [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html)
-              country code which replaces automatic phone-number detection
-              without a country code.
-
-              As an example, with `DE` set as the default phone region,
-              the `+49` prefix can be omitted for phone numbers.
-            '';
-          };
-          "profile.enabled" = mkEnableOption "global profiles" // {
-            description = ''
-              Makes user-profiles globally available under `nextcloud.tld/u/user.name`.
-              Even though it's enabled by default in Nextcloud, it must be explicitly enabled
-              here because it has the side-effect that personal information is even accessible to
-              unauthenticated users by default.
-              By default, the following properties are set to “Show to everyone”
-              if this flag is enabled:
-              - About
-              - Full name
-              - Headline
-              - Organisation
-              - Profile picture
-              - Role
-              - Twitter
-              - Website
-              Only has an effect in Nextcloud 23 and later.
-            '';
-          };
-        };
-      };
-      default = {};
-      description = ''
-        Extra options which should be appended to Nextcloud's config.php file.
-      '';
-      example = literalExpression '' {
-        redis = {
-          host = "/run/redis/redis.sock";
-          port = 0;
-          dbindex = 0;
-          password = "secret";
-          timeout = 1.5;
-        };
-      } '';
-    };
-
-    secretFile = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      description = ''
-        Secret options which will be appended to Nextcloud's config.php file (written as JSON, in the same
-        form as the [](#opt-services.nextcloud.settings) option), for example
-        `{"redis":{"password":"secret"}}`.
-      '';
-    };
-
-    nginx = {
-      recommendedHttpHeaders = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Enable additional recommended HTTP response headers";
-      };
-      hstsMaxAge = mkOption {
-        type = types.ints.positive;
-        default = 15552000;
-        description = ''
-          Value for the `max-age` directive of the HTTP
-          `Strict-Transport-Security` header.
-
-          See section 6.1.1 of IETF RFC 6797 for detailed information on this
-          directive and header.
-        '';
-      };
-    };
-
-    cron.memoryLimit = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "1G";
-      description = ''
-        The `memory_limit` of PHP is equal to [](#opt-services.nextcloud.maxUploadSize).
-        The value can be customized for `nextcloud-cron.service` using this option.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable (mkMerge [
-    { warnings = let
-        latest = 29;
-        upgradeWarning = major: nixos:
-          ''
-            A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
-
-            After nextcloud${toString major} is installed successfully, you can safely upgrade
-            to ${toString (major + 1)}. The latest version available is Nextcloud${toString latest}.
-
-            Please note that Nextcloud doesn't support upgrades across multiple major versions
-            (i.e. an upgrade from 16 is possible to 17, but not 16 to 18).
-
-            The package can be upgraded by explicitly declaring the service-option
-            `services.nextcloud.package`.
-          '';
-
-      in (optional (cfg.poolConfig != null) ''
-          Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
-          Please migrate your configuration to config.services.nextcloud.poolSettings.
-        '')
-        ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11"))
-        ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05"))
-        ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11"))
-        ++ (optional (versionOlder cfg.package.version "28") (upgradeWarning 27 "24.05"))
-        ++ (optional (versionOlder cfg.package.version "29") (upgradeWarning 28 "24.11"));
-
-      services.nextcloud.package = with pkgs;
-        mkDefault (
-          if pkgs ? nextcloud
-            then throw ''
-              The `pkgs.nextcloud`-attribute has been removed. If it's supposed to be the default
-              nextcloud defined in an overlay, please set `services.nextcloud.package` to
-              `pkgs.nextcloud`.
-            ''
-          else if versionOlder stateVersion "23.05" then nextcloud25
-          else if versionOlder stateVersion "23.11" then nextcloud26
-          else if versionOlder stateVersion "24.05" then nextcloud27
-          else nextcloud29
-        );
-
-      services.nextcloud.phpPackage =
-        if versionOlder cfg.package.version "29" then pkgs.php82
-        else pkgs.php83;
-
-      services.nextcloud.phpOptions = mkMerge [
-        (mapAttrs (const mkOptionDefault) defaultPHPSettings)
-        {
-          upload_max_filesize = cfg.maxUploadSize;
-          post_max_size = cfg.maxUploadSize;
-          memory_limit = cfg.maxUploadSize;
-        }
-        (mkIf cfg.caching.apcu {
-          "apc.enable_cli" = "1";
-        })
-      ];
-    }
-
-    { assertions = [
-      { assertion = cfg.database.createLocally -> cfg.config.dbpassFile == null;
-        message = ''
-          Using `services.nextcloud.database.createLocally` with database
-          password authentication is no longer supported.
-
-          If you use an external database (or want to use password auth for any
-          other reason), set `services.nextcloud.database.createLocally` to
-          `false`. The database won't be managed for you (use `services.mysql`
-          if you want to set it up).
-
-          If you want this module to manage your nextcloud database for you,
-          unset `services.nextcloud.config.dbpassFile` and
-          `services.nextcloud.config.dbhost` to use socket authentication
-          instead of password.
-        '';
-      }
-    ]; }
-
-    { systemd.timers.nextcloud-cron = {
-        wantedBy = [ "timers.target" ];
-        after = [ "nextcloud-setup.service" ];
-        timerConfig = {
-          OnBootSec = "5m";
-          OnUnitActiveSec = "5m";
-          Unit = "nextcloud-cron.service";
-        };
-      };
-
-      systemd.tmpfiles.rules = map (dir: "d ${dir} 0750 nextcloud nextcloud - -") [
-        "${cfg.home}"
-        "${datadir}/config"
-        "${datadir}/data"
-        "${cfg.home}/store-apps"
-      ] ++ [
-        "L+ ${datadir}/config/override.config.php - - - - ${overrideConfig}"
-      ];
-
-      systemd.services = {
-        # When upgrading the Nextcloud package, Nextcloud can report errors such as
-        # "The files of the app [all apps in /var/lib/nextcloud/apps] were not replaced correctly"
-        # Restarting phpfpm on Nextcloud package update fixes these issues (but this is a workaround).
-        phpfpm-nextcloud.restartTriggers = [ webroot overrideConfig ];
-
-        nextcloud-setup = let
-          c = cfg.config;
-          occInstallCmd = let
-            mkExport = { arg, value }: "export ${arg}=${value}";
-            dbpass = {
-              arg = "DBPASS";
-              value = if c.dbpassFile != null
-                then ''"$(<"${toString c.dbpassFile}")"''
-                else ''""'';
-            };
-            adminpass = {
-              arg = "ADMINPASS";
-              value = ''"$(<"${toString c.adminpassFile}")"'';
-            };
-            installFlags = concatStringsSep " \\\n    "
-              (mapAttrsToList (k: v: "${k} ${toString v}") {
-              "--database" = ''"${c.dbtype}"'';
-              # The following attributes are optional depending on the type of
-              # database.  Those that evaluate to null on the left hand side
-              # will be omitted.
-              ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
-              ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
-              ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
-              "--database-pass" = "\"\$${dbpass.arg}\"";
-              "--admin-user" = ''"${c.adminuser}"'';
-              "--admin-pass" = "\"\$${adminpass.arg}\"";
-              "--data-dir" = ''"${datadir}/data"'';
-            });
-          in ''
-            ${mkExport dbpass}
-            ${mkExport adminpass}
-            ${occ}/bin/nextcloud-occ maintenance:install \
-                ${installFlags}
-          '';
-          occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0
-            (i: v: ''
-              ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
-                ${toString i} --value="${toString v}"
-            '') ([ cfg.hostName ] ++ cfg.settings.trusted_domains));
-
-        in {
-          wantedBy = [ "multi-user.target" ];
-          wants = [ "nextcloud-update-db.service" ];
-          before = [ "phpfpm-nextcloud.service" ];
-          after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
-          requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
-          path = [ occ ];
-          restartTriggers = [ overrideConfig ];
-          script = ''
-            ${optionalString (c.dbpassFile != null) ''
-              if [ ! -r "${c.dbpassFile}" ]; then
-                echo "dbpassFile ${c.dbpassFile} is not readable by nextcloud:nextcloud! Aborting..."
-                exit 1
-              fi
-              if [ -z "$(<${c.dbpassFile})" ]; then
-                echo "dbpassFile ${c.dbpassFile} is empty!"
-                exit 1
-              fi
-            ''}
-            if [ ! -r "${c.adminpassFile}" ]; then
-              echo "adminpassFile ${c.adminpassFile} is not readable by nextcloud:nextcloud! Aborting..."
-              exit 1
-            fi
-            if [ -z "$(<${c.adminpassFile})" ]; then
-              echo "adminpassFile ${c.adminpassFile} is empty!"
-              exit 1
-            fi
-
-            ${concatMapStrings (name: ''
-              if [ -d "${cfg.home}"/${name} ]; then
-                echo "Cleaning up ${name}; these are now bundled in the webroot store-path!"
-                rm -r "${cfg.home}"/${name}
-              fi
-            '') [ "nix-apps" "apps" ]}
-
-            # Do not install if already installed
-            if [[ ! -e ${datadir}/config/config.php ]]; then
-              ${occInstallCmd}
-            fi
-
-            ${occ}/bin/nextcloud-occ upgrade
-
-            ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
-
-            ${optionalString (cfg.extraAppsEnable && cfg.extraApps != { }) ''
-                # Try to enable apps
-                ${occ}/bin/nextcloud-occ app:enable ${concatStringsSep " " (attrNames cfg.extraApps)}
-            ''}
-
-            ${occSetTrustedDomainsCmd}
-          '';
-          serviceConfig.Type = "oneshot";
-          serviceConfig.User = "nextcloud";
-          # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent
-          # an automatic creation of the database user.
-          environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false";
-        };
-        nextcloud-cron = {
-          after = [ "nextcloud-setup.service" ];
-          environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
-          serviceConfig = {
-            Type = "exec";
-            User = "nextcloud";
-            ExecCondition = "${lib.getExe phpPackage} -f ${webroot}/occ status -e";
-            ExecStart = lib.concatStringsSep " " ([
-              (lib.getExe phpPackage)
-            ] ++ optional (cfg.cron.memoryLimit != null) "-dmemory_limit=${cfg.cron.memoryLimit}"
-              ++ [
-              "-f"
-              "${webroot}/cron.php"
-            ]);
-            KillMode = "process";
-          };
-        };
-        nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable {
-          after = [ "nextcloud-setup.service" ];
-          serviceConfig = {
-            Type = "oneshot";
-            ExecStart = "${occ}/bin/nextcloud-occ app:update --all";
-            User = "nextcloud";
-          };
-          startAt = cfg.autoUpdateApps.startAt;
-        };
-        nextcloud-update-db = {
-          after = [ "nextcloud-setup.service" ];
-          environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
-          script = ''
-            ${occ}/bin/nextcloud-occ db:add-missing-columns
-            ${occ}/bin/nextcloud-occ db:add-missing-indices
-            ${occ}/bin/nextcloud-occ db:add-missing-primary-keys
-          '';
-          serviceConfig = {
-            Type = "exec";
-            User = "nextcloud";
-            ExecCondition = "${lib.getExe phpPackage} -f ${webroot}/occ status -e";
-          };
-        };
-      };
-
-      services.phpfpm = {
-        pools.nextcloud = {
-          user = "nextcloud";
-          group = "nextcloud";
-          phpPackage = phpPackage;
-          phpEnv = {
-            NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
-            PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
-          };
-          settings = mapAttrs (name: mkDefault) {
-            "listen.owner" = config.services.nginx.user;
-            "listen.group" = config.services.nginx.group;
-          } // cfg.poolSettings;
-          extraConfig = cfg.poolConfig;
-        };
-      };
-
-      users.users.nextcloud = {
-        home = "${cfg.home}";
-        group = "nextcloud";
-        isSystemUser = true;
-      };
-      users.groups.nextcloud.members = [ "nextcloud" config.services.nginx.user ];
-
-      environment.systemPackages = [ occ ];
-
-      services.mysql = lib.mkIf mysqlLocal {
-        enable = true;
-        package = lib.mkDefault pkgs.mariadb;
-        ensureDatabases = [ cfg.config.dbname ];
-        ensureUsers = [{
-          name = cfg.config.dbuser;
-          ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; };
-        }];
-      };
-
-      services.postgresql = mkIf pgsqlLocal {
-        enable = true;
-        ensureDatabases = [ cfg.config.dbname ];
-        ensureUsers = [{
-          name = cfg.config.dbuser;
-          ensureDBOwnership = true;
-        }];
-      };
-
-      services.redis.servers.nextcloud = lib.mkIf cfg.configureRedis {
-        enable = true;
-        user = "nextcloud";
-      };
-
-      services.nextcloud = {
-        caching.redis = lib.mkIf cfg.configureRedis true;
-        settings = mkMerge [({
-          datadirectory = lib.mkDefault "${datadir}/data";
-          trusted_domains = [ cfg.hostName ];
-        }) (lib.mkIf cfg.configureRedis {
-          "memcache.distributed" = ''\OC\Memcache\Redis'';
-          "memcache.locking" = ''\OC\Memcache\Redis'';
-          redis = {
-            host = config.services.redis.servers.nextcloud.unixSocket;
-            port = 0;
-          };
-        })];
-      };
-
-      services.nginx.enable = mkDefault true;
-
-      services.nginx.virtualHosts.${cfg.hostName} = {
-        root = webroot;
-        locations = {
-          "= /robots.txt" = {
-            priority = 100;
-            extraConfig = ''
-              allow all;
-              access_log off;
-            '';
-          };
-          "= /" = {
-            priority = 100;
-            extraConfig = ''
-              if ( $http_user_agent ~ ^DavClnt ) {
-                return 302 /remote.php/webdav/$is_args$args;
-              }
-            '';
-          };
-          "^~ /.well-known" = {
-            priority = 210;
-            extraConfig = ''
-              absolute_redirect off;
-              location = /.well-known/carddav {
-                return 301 /remote.php/dav/;
-              }
-              location = /.well-known/caldav {
-                return 301 /remote.php/dav/;
-              }
-              location ~ ^/\.well-known/(?!acme-challenge|pki-validation) {
-                return 301 /index.php$request_uri;
-              }
-              try_files $uri $uri/ =404;
-            '';
-          };
-          "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)" = {
-            priority = 450;
-            extraConfig = ''
-              return 404;
-            '';
-          };
-          "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
-            priority = 450;
-            extraConfig = ''
-              return 404;
-            '';
-          };
-          "~ \\.php(?:$|/)" = {
-            priority = 500;
-            extraConfig = ''
-              # legacy support (i.e. static files and directories in cfg.package)
-              rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[s${optionalString (!ocmProviderIsNotAStaticDirAnymore) "m"}]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;
-              include ${config.services.nginx.package}/conf/fastcgi.conf;
-              fastcgi_split_path_info ^(.+?\.php)(\\/.*)$;
-              set $path_info $fastcgi_path_info;
-              try_files $fastcgi_script_name =404;
-              fastcgi_param PATH_INFO $path_info;
-              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-              fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
-              fastcgi_param modHeadersAvailable true;
-              fastcgi_param front_controller_active true;
-              fastcgi_pass unix:${fpm.socket};
-              fastcgi_intercept_errors on;
-              fastcgi_request_buffering off;
-              fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s;
-            '';
-          };
-          "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm|ogg|flac)$".extraConfig = ''
-            try_files $uri /index.php$request_uri;
-            expires 6M;
-            access_log off;
-            location ~ \.mjs$ {
-              default_type text/javascript;
-            }
-            location ~ \.wasm$ {
-              default_type application/wasm;
-            }
-          '';
-          "~ ^\\/(?:updater|ocs-provider${optionalString (!ocmProviderIsNotAStaticDirAnymore) "|ocm-provider"})(?:$|\\/)".extraConfig = ''
-            try_files $uri/ =404;
-            index index.php;
-          '';
-          "/remote" = {
-            priority = 1500;
-            extraConfig = ''
-              return 301 /remote.php$request_uri;
-            '';
-          };
-          "/" = {
-            priority = 1600;
-            extraConfig = ''
-              try_files $uri $uri/ /index.php$request_uri;
-            '';
-          };
-        };
-        extraConfig = ''
-          index index.php index.html /index.php$request_uri;
-          ${optionalString (cfg.nginx.recommendedHttpHeaders) ''
-            add_header X-Content-Type-Options nosniff;
-            add_header X-XSS-Protection "1; mode=block";
-            add_header X-Robots-Tag "noindex, nofollow";
-            add_header X-Download-Options noopen;
-            add_header X-Permitted-Cross-Domain-Policies none;
-            add_header X-Frame-Options sameorigin;
-            add_header Referrer-Policy no-referrer;
-          ''}
-          ${optionalString (cfg.https) ''
-            add_header Strict-Transport-Security "max-age=${toString cfg.nginx.hstsMaxAge}; includeSubDomains" always;
-          ''}
-          client_max_body_size ${cfg.maxUploadSize};
-          fastcgi_buffers 64 4K;
-          fastcgi_hide_header X-Powered-By;
-          gzip on;
-          gzip_vary on;
-          gzip_comp_level 4;
-          gzip_min_length 256;
-          gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
-          gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
-
-          ${optionalString cfg.webfinger ''
-            rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
-            rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
-          ''}
-        '';
-      };
-    }
-  ]);
-
-  meta.doc = ./nextcloud.md;
-}
diff --git a/nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix b/nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix
deleted file mode 100644
index 9bd2cf310c0a..000000000000
--- a/nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix
+++ /dev/null
@@ -1,87 +0,0 @@
-{
-  config,
-  pkgs,
-  lib,
-  ...
-}:
-let
-  cfg = config.services.nextjs-ollama-llm-ui;
-  # we have to override the URL to a Ollama service here, because it gets baked into the web app.
-  nextjs-ollama-llm-ui = cfg.package.override { inherit (cfg) ollamaUrl; };
-in
-{
-  options = {
-    services.nextjs-ollama-llm-ui = {
-      enable = lib.mkEnableOption ''
-        Simple Ollama web UI service; an easy to use web frontend for a Ollama backend service.
-        Run state-of-the-art AI large language models (LLM) similar to ChatGPT locally with privacy
-        on your personal computer.
-        This service is stateless and doesn't store any data on the server; all data is kept
-        locally in your web browser.
-        See https://github.com/jakobhoeg/nextjs-ollama-llm-ui.
-
-        Required: You need the Ollama backend service running by having
-        "services.nextjs-ollama-llm-ui.ollamaUrl" point to the correct url.
-        You can host such a backend service with NixOS through "services.ollama".
-      '';
-      package = lib.mkPackageOption pkgs "nextjs-ollama-llm-ui" { };
-
-      hostname = lib.mkOption {
-        type = lib.types.str;
-        default = "127.0.0.1";
-        example = "ui.example.org";
-        description = ''
-          The hostname under which the Ollama UI interface should be accessible.
-          By default it uses localhost/127.0.0.1 to be accessible only from the local machine.
-          Change to "0.0.0.0" to make it directly accessible from the local network.
-
-          Note: You should keep it at 127.0.0.1 and only serve to the local
-          network or internet from a (home) server behind a reverse-proxy and secured encryption.
-          See https://wiki.nixos.org/wiki/Nginx for instructions on how to set up a reverse-proxy.
-        '';
-      };
-
-      port = lib.mkOption {
-        type = lib.types.port;
-        default = 3000;
-        example = 3000;
-        description = ''
-          The port under which the Ollama UI interface should be accessible.
-        '';
-      };
-
-      ollamaUrl = lib.mkOption {
-        type = lib.types.str;
-        default = "127.0.0.1:11434";
-        example = "https://ollama.example.org";
-        description = ''
-          The address (including host and port) under which we can access the Ollama backend server.
-          !Note that if the the UI service is running under a domain "https://ui.example.org",
-          the Ollama backend service must allow "CORS" requests from this domain, e.g. by adding
-          "services.ollama.environment.OLLAMA_ORIGINS = [ ... "https://ui.example.org" ];"!
-        '';
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services = {
-
-      nextjs-ollama-llm-ui = {
-        wantedBy = [ "multi-user.target" ];
-        description = "Nextjs Ollama LLM Ui.";
-        after = [ "network.target" ];
-        environment = {
-          HOSTNAME = cfg.hostname;
-          PORT = toString cfg.port;
-          NEXT_PUBLIC_OLLAMA_URL = cfg.ollamaUrl;
-        };
-        serviceConfig = {
-          ExecStart = "${lib.getExe nextjs-ollama-llm-ui}";
-          DynamicUser = true;
-        };
-      };
-    };
-  };
-  meta.maintainers = with lib.maintainers; [ malteneuss ];
-}
diff --git a/nixos/modules/services/web-apps/nexus.nix b/nixos/modules/services/web-apps/nexus.nix
deleted file mode 100644
index fdf42ace6b0e..000000000000
--- a/nixos/modules/services/web-apps/nexus.nix
+++ /dev/null
@@ -1,152 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.nexus;
-
-in
-{
-  options = {
-    services.nexus = {
-      enable = mkEnableOption "Sonatype Nexus3 OSS service";
-
-      package = lib.mkPackageOption pkgs "nexus" { };
-
-      jdkPackage = lib.mkPackageOption pkgs "openjdk8" { };
-
-      user = mkOption {
-        type = types.str;
-        default = "nexus";
-        description = "User which runs Nexus3.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "nexus";
-        description = "Group which runs Nexus3.";
-      };
-
-      home = mkOption {
-        type = types.str;
-        default = "/var/lib/sonatype-work";
-        description = "Home directory of the Nexus3 instance.";
-      };
-
-      listenAddress = mkOption {
-        type = types.str;
-        default = "127.0.0.1";
-        description = "Address to listen on.";
-      };
-
-      listenPort = mkOption {
-        type = types.int;
-        default = 8081;
-        description = "Port to listen on.";
-      };
-
-      jvmOpts = mkOption {
-        type = types.lines;
-        default = ''
-          -Xms1200M
-          -Xmx1200M
-          -XX:MaxDirectMemorySize=2G
-          -XX:+UnlockDiagnosticVMOptions
-          -XX:+UnsyncloadClass
-          -XX:+LogVMOutput
-          -XX:LogFile=${cfg.home}/nexus3/log/jvm.log
-          -XX:-OmitStackTraceInFastThrow
-          -Djava.net.preferIPv4Stack=true
-          -Dkaraf.home=${cfg.package}
-          -Dkaraf.base=${cfg.package}
-          -Dkaraf.etc=${cfg.package}/etc/karaf
-          -Djava.util.logging.config.file=${cfg.package}/etc/karaf/java.util.logging.properties
-          -Dkaraf.data=${cfg.home}/nexus3
-          -Djava.io.tmpdir=${cfg.home}/nexus3/tmp
-          -Dkaraf.startLocalConsole=false
-          -Djava.endorsed.dirs=${cfg.package}/lib/endorsed
-        '';
-        defaultText = literalExpression ''
-          '''
-            -Xms1200M
-            -Xmx1200M
-            -XX:MaxDirectMemorySize=2G
-            -XX:+UnlockDiagnosticVMOptions
-            -XX:+UnsyncloadClass
-            -XX:+LogVMOutput
-            -XX:LogFile=''${home}/nexus3/log/jvm.log
-            -XX:-OmitStackTraceInFastThrow
-            -Djava.net.preferIPv4Stack=true
-            -Dkaraf.home=''${package}
-            -Dkaraf.base=''${package}
-            -Dkaraf.etc=''${package}/etc/karaf
-            -Djava.util.logging.config.file=''${package}/etc/karaf/java.util.logging.properties
-            -Dkaraf.data=''${home}/nexus3
-            -Djava.io.tmpdir=''${home}/nexus3/tmp
-            -Dkaraf.startLocalConsole=false
-            -Djava.endorsed.dirs=''${package}/lib/endorsed
-          '''
-        '';
-
-        description = ''
-          Options for the JVM written to `nexus.jvmopts`.
-          Please refer to the docs (https://help.sonatype.com/repomanager3/installation/configuring-the-runtime-environment)
-          for further information.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    users.users.${cfg.user} = {
-      isSystemUser = true;
-      inherit (cfg) group home;
-      createHome = true;
-    };
-
-    users.groups.${cfg.group} = { };
-
-    systemd.services.nexus = {
-      description = "Sonatype Nexus3";
-
-      wantedBy = [ "multi-user.target" ];
-
-      path = [ cfg.home ];
-
-      environment = {
-        NEXUS_USER = cfg.user;
-        NEXUS_HOME = cfg.home;
-
-        INSTALL4J_JAVA_HOME = cfg.jdkPackage;
-        VM_OPTS_FILE = pkgs.writeText "nexus.vmoptions" cfg.jvmOpts;
-      };
-
-      preStart = ''
-        mkdir -p ${cfg.home}/nexus3/etc
-
-        if [ ! -f ${cfg.home}/nexus3/etc/nexus.properties ]; then
-          echo "# Jetty section" > ${cfg.home}/nexus3/etc/nexus.properties
-          echo "application-port=${toString cfg.listenPort}" >> ${cfg.home}/nexus3/etc/nexus.properties
-          echo "application-host=${toString cfg.listenAddress}" >> ${cfg.home}/nexus3/etc/nexus.properties
-        else
-          sed 's/^application-port=.*/application-port=${toString cfg.listenPort}/' -i ${cfg.home}/nexus3/etc/nexus.properties
-          sed 's/^# application-port=.*/application-port=${toString cfg.listenPort}/' -i ${cfg.home}/nexus3/etc/nexus.properties
-          sed 's/^application-host=.*/application-host=${toString cfg.listenAddress}/' -i ${cfg.home}/nexus3/etc/nexus.properties
-          sed 's/^# application-host=.*/application-host=${toString cfg.listenAddress}/' -i ${cfg.home}/nexus3/etc/nexus.properties
-        fi
-      '';
-
-      script = "${cfg.package}/bin/nexus run";
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        PrivateTmp = true;
-        LimitNOFILE = 102642;
-      };
-    };
-  };
-
-  meta.maintainers = with lib.maintainers; [ ironpinguin ];
-}
diff --git a/nixos/modules/services/web-apps/nifi.nix b/nixos/modules/services/web-apps/nifi.nix
deleted file mode 100644
index 48de6b1495ab..000000000000
--- a/nixos/modules/services/web-apps/nifi.nix
+++ /dev/null
@@ -1,321 +0,0 @@
-{ lib, pkgs, config, options, ... }:
-
-let
-  cfg = config.services.nifi;
-  opt = options.services.nifi;
-
-  env = {
-    NIFI_OVERRIDE_NIFIENV = "true";
-    NIFI_HOME = "/var/lib/nifi";
-    NIFI_PID_DIR = "/run/nifi";
-    NIFI_LOG_DIR = "/var/log/nifi";
-  };
-
-  envFile = pkgs.writeText "nifi.env" (lib.concatMapStrings (s: s + "\n") (
-    (lib.concatLists (lib.mapAttrsToList (name: value:
-      lib.optional (value != null) ''${name}="${toString value}"''
-    ) env))));
-
-  nifiEnv = pkgs.writeShellScriptBin "nifi-env" ''
-    set -a
-    source "${envFile}"
-    eval -- "\$@"
-  '';
-
-in {
-  options = {
-    services.nifi = {
-      enable = lib.mkEnableOption "Apache NiFi";
-
-      package = lib.mkOption {
-        type = lib.types.package;
-        default = pkgs.nifi;
-        defaultText = lib.literalExpression "pkgs.nifi";
-        description = "Apache NiFi package to use.";
-      };
-
-      user = lib.mkOption {
-        type = lib.types.str;
-        default = "nifi";
-        description = "User account where Apache NiFi runs.";
-      };
-
-      group = lib.mkOption {
-        type = lib.types.str;
-        default = "nifi";
-        description = "Group account where Apache NiFi runs.";
-      };
-
-      enableHTTPS = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = "Enable HTTPS protocol. Don`t use in production.";
-      };
-
-      listenHost = lib.mkOption {
-        type = lib.types.str;
-        default = if cfg.enableHTTPS then "0.0.0.0" else "127.0.0.1";
-        defaultText = lib.literalExpression ''
-          if config.${opt.enableHTTPS}
-          then "0.0.0.0"
-          else "127.0.0.1"
-        '';
-        description = "Bind to an ip for Apache NiFi web-ui.";
-      };
-
-      listenPort = lib.mkOption {
-        type = lib.types.int;
-        default = if cfg.enableHTTPS then 8443 else 8080;
-        defaultText = lib.literalExpression ''
-          if config.${opt.enableHTTPS}
-          then "8443"
-          else "8000"
-        '';
-        description = "Bind to a port for Apache NiFi web-ui.";
-      };
-
-      proxyHost = lib.mkOption {
-        type = lib.types.nullOr lib.types.str;
-        default = if cfg.enableHTTPS then "0.0.0.0" else null;
-        defaultText = lib.literalExpression ''
-          if config.${opt.enableHTTPS}
-          then "0.0.0.0"
-          else null
-        '';
-        description = "Allow requests from a specific host.";
-      };
-
-      proxyPort = lib.mkOption {
-        type = lib.types.nullOr lib.types.int;
-        default = if cfg.enableHTTPS then 8443 else null;
-        defaultText = lib.literalExpression ''
-          if config.${opt.enableHTTPS}
-          then "8443"
-          else null
-        '';
-        description = "Allow requests from a specific port.";
-      };
-
-      initUser = lib.mkOption {
-        type = lib.types.nullOr lib.types.str;
-        default = null;
-        description = "Initial user account for Apache NiFi. Username must be at least 4 characters.";
-      };
-
-      initPasswordFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/run/keys/nifi/password-nifi";
-        description = "nitial password for Apache NiFi. Password must be at least 12 characters.";
-      };
-
-      initJavaHeapSize = lib.mkOption {
-        type = lib.types.nullOr lib.types.int;
-        default = null;
-        example = 1024;
-        description = "Set the initial heap size for the JVM in MB.";
-      };
-
-      maxJavaHeapSize = lib.mkOption {
-        type = lib.types.nullOr lib.types.int;
-        default = null;
-        example = 2048;
-        description = "Set the initial heap size for the JVM in MB.";
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    assertions = [
-      { assertion = cfg.initUser!=null || cfg.initPasswordFile==null;
-          message = ''
-            <option>services.nifi.initUser</option> needs to be set if <option>services.nifi.initPasswordFile</option> enabled.
-          '';
-      }
-      { assertion = cfg.initUser==null || cfg.initPasswordFile!=null;
-          message = ''
-            <option>services.nifi.initPasswordFile</option> needs to be set if <option>services.nifi.initUser</option> enabled.
-          '';
-      }
-      { assertion = cfg.proxyHost==null || cfg.proxyPort!=null;
-          message = ''
-            <option>services.nifi.proxyPort</option> needs to be set if <option>services.nifi.proxyHost</option> value specified.
-          '';
-      }
-      { assertion = cfg.proxyHost!=null || cfg.proxyPort==null;
-          message = ''
-            <option>services.nifi.proxyHost</option> needs to be set if <option>services.nifi.proxyPort</option> value specified.
-          '';
-      }
-      { assertion = cfg.initJavaHeapSize==null || cfg.maxJavaHeapSize!=null;
-          message = ''
-            <option>services.nifi.maxJavaHeapSize</option> needs to be set if <option>services.nifi.initJavaHeapSize</option> value specified.
-          '';
-      }
-      { assertion = cfg.initJavaHeapSize!=null || cfg.maxJavaHeapSize==null;
-          message = ''
-            <option>services.nifi.initJavaHeapSize</option> needs to be set if <option>services.nifi.maxJavaHeapSize</option> value specified.
-          '';
-      }
-    ];
-
-    warnings = lib.optional (cfg.enableHTTPS==false) ''
-      Please do not disable HTTPS mode in production. In this mode, access to the nifi is opened without authentication.
-    '';
-
-    systemd.tmpfiles.settings."10-nifi" = {
-      "/var/lib/nifi/conf".d = {
-        inherit (cfg) user group;
-        mode = "0750";
-      };
-      "/var/lib/nifi/lib"."L+" = {
-        argument = "${cfg.package}/lib";
-      };
-    };
-
-
-    systemd.services.nifi = {
-      description = "Apache NiFi";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      environment = env;
-      path = [ pkgs.gawk ];
-
-      serviceConfig = {
-        Type = "forking";
-        PIDFile = "/run/nifi/nifi.pid";
-        ExecStartPre = pkgs.writeScript "nifi-pre-start.sh" ''
-          #!/bin/sh
-          umask 077
-          test -f '/var/lib/nifi/conf/authorizers.xml'                      || (cp '${cfg.package}/share/nifi/conf/authorizers.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/authorizers.xml')
-          test -f '/var/lib/nifi/conf/bootstrap.conf'                       || (cp '${cfg.package}/share/nifi/conf/bootstrap.conf' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/bootstrap.conf')
-          test -f '/var/lib/nifi/conf/bootstrap-hashicorp-vault.conf'       || (cp '${cfg.package}/share/nifi/conf/bootstrap-hashicorp-vault.conf' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/bootstrap-hashicorp-vault.conf')
-          test -f '/var/lib/nifi/conf/bootstrap-notification-services.xml'  || (cp '${cfg.package}/share/nifi/conf/bootstrap-notification-services.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/bootstrap-notification-services.xml')
-          test -f '/var/lib/nifi/conf/logback.xml'                          || (cp '${cfg.package}/share/nifi/conf/logback.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/logback.xml')
-          test -f '/var/lib/nifi/conf/login-identity-providers.xml'         || (cp '${cfg.package}/share/nifi/conf/login-identity-providers.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/login-identity-providers.xml')
-          test -f '/var/lib/nifi/conf/nifi.properties'                      || (cp '${cfg.package}/share/nifi/conf/nifi.properties' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/nifi.properties')
-          test -f '/var/lib/nifi/conf/stateless-logback.xml'                || (cp '${cfg.package}/share/nifi/conf/stateless-logback.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/stateless-logback.xml')
-          test -f '/var/lib/nifi/conf/stateless.properties'                 || (cp '${cfg.package}/share/nifi/conf/stateless.properties' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/stateless.properties')
-          test -f '/var/lib/nifi/conf/state-management.xml'                 || (cp '${cfg.package}/share/nifi/conf/state-management.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/state-management.xml')
-          test -f '/var/lib/nifi/conf/zookeeper.properties'                 || (cp '${cfg.package}/share/nifi/conf/zookeeper.properties' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/zookeeper.properties')
-          test -d '/var/lib/nifi/docs/html'                                 || (mkdir -p /var/lib/nifi/docs && cp -r '${cfg.package}/share/nifi/docs/html' '/var/lib/nifi/docs/html')
-          ${lib.optionalString ((cfg.initUser != null) && (cfg.initPasswordFile != null)) ''
-            awk -F'[<|>]' '/property name="Username"/ {if ($3!="") f=1} END{exit !f}' /var/lib/nifi/conf/login-identity-providers.xml || ${cfg.package}/bin/nifi.sh set-single-user-credentials ${cfg.initUser} $(cat ${cfg.initPasswordFile})
-          ''}
-          ${lib.optionalString (cfg.enableHTTPS == false) ''
-            sed -i /var/lib/nifi/conf/nifi.properties \
-              -e 's|nifi.remote.input.secure=.*|nifi.remote.input.secure=false|g' \
-              -e 's|nifi.web.http.host=.*|nifi.web.http.host=${cfg.listenHost}|g' \
-              -e 's|nifi.web.http.port=.*|nifi.web.http.port=${(toString cfg.listenPort)}|g' \
-              -e 's|nifi.web.https.host=.*|nifi.web.https.host=|g' \
-              -e 's|nifi.web.https.port=.*|nifi.web.https.port=|g' \
-              -e 's|nifi.security.keystore=.*|nifi.security.keystore=|g' \
-              -e 's|nifi.security.keystoreType=.*|nifi.security.keystoreType=|g' \
-              -e 's|nifi.security.truststore=.*|nifi.security.truststore=|g' \
-              -e 's|nifi.security.truststoreType=.*|nifi.security.truststoreType=|g' \
-              -e '/nifi.security.keystorePasswd/s|^|#|' \
-              -e '/nifi.security.keyPasswd/s|^|#|' \
-              -e '/nifi.security.truststorePasswd/s|^|#|'
-          ''}
-          ${lib.optionalString (cfg.enableHTTPS == true) ''
-            sed -i /var/lib/nifi/conf/nifi.properties \
-              -e 's|nifi.remote.input.secure=.*|nifi.remote.input.secure=true|g' \
-              -e 's|nifi.web.http.host=.*|nifi.web.http.host=|g' \
-              -e 's|nifi.web.http.port=.*|nifi.web.http.port=|g' \
-              -e 's|nifi.web.https.host=.*|nifi.web.https.host=${cfg.listenHost}|g' \
-              -e 's|nifi.web.https.port=.*|nifi.web.https.port=${(toString cfg.listenPort)}|g' \
-              -e 's|nifi.security.keystore=.*|nifi.security.keystore=./conf/keystore.p12|g' \
-              -e 's|nifi.security.keystoreType=.*|nifi.security.keystoreType=PKCS12|g' \
-              -e 's|nifi.security.truststore=.*|nifi.security.truststore=./conf/truststore.p12|g' \
-              -e 's|nifi.security.truststoreType=.*|nifi.security.truststoreType=PKCS12|g' \
-              -e '/nifi.security.keystorePasswd/s|^#\+||' \
-              -e '/nifi.security.keyPasswd/s|^#\+||' \
-              -e '/nifi.security.truststorePasswd/s|^#\+||'
-          ''}
-          ${lib.optionalString ((cfg.enableHTTPS == true) && (cfg.proxyHost != null) && (cfg.proxyPort != null)) ''
-            sed -i /var/lib/nifi/conf/nifi.properties \
-              -e 's|nifi.web.proxy.host=.*|nifi.web.proxy.host=${cfg.proxyHost}:${(toString cfg.proxyPort)}|g'
-          ''}
-          ${lib.optionalString ((cfg.enableHTTPS == false) || (cfg.proxyHost == null) && (cfg.proxyPort == null)) ''
-            sed -i /var/lib/nifi/conf/nifi.properties \
-              -e 's|nifi.web.proxy.host=.*|nifi.web.proxy.host=|g'
-          ''}
-          ${lib.optionalString ((cfg.initJavaHeapSize != null) && (cfg.maxJavaHeapSize != null))''
-            sed -i /var/lib/nifi/conf/bootstrap.conf \
-              -e 's|java.arg.2=.*|java.arg.2=-Xms${(toString cfg.initJavaHeapSize)}m|g' \
-              -e 's|java.arg.3=.*|java.arg.3=-Xmx${(toString cfg.maxJavaHeapSize)}m|g'
-          ''}
-          ${lib.optionalString ((cfg.initJavaHeapSize == null) && (cfg.maxJavaHeapSize == null))''
-            sed -i /var/lib/nifi/conf/bootstrap.conf \
-              -e 's|java.arg.2=.*|java.arg.2=-Xms512m|g' \
-              -e 's|java.arg.3=.*|java.arg.3=-Xmx512m|g'
-          ''}
-        '';
-        ExecStart = "${cfg.package}/bin/nifi.sh start";
-        ExecStop = "${cfg.package}/bin/nifi.sh stop";
-        # User and group
-        User = cfg.user;
-        Group = cfg.group;
-        # Runtime directory and mode
-        RuntimeDirectory = "nifi";
-        RuntimeDirectoryMode = "0750";
-        # State directory and mode
-        StateDirectory = "nifi";
-        StateDirectoryMode = "0750";
-        # Logs directory and mode
-        LogsDirectory = "nifi";
-        LogsDirectoryMode = "0750";
-        # Proc filesystem
-        ProcSubset = "pid";
-        ProtectProc = "invisible";
-        # Access write directories
-        ReadWritePaths = [ cfg.initPasswordFile ];
-        UMask = "0027";
-        # Capabilities
-        CapabilityBoundingSet = "";
-        # Security
-        NoNewPrivileges = true;
-        # Sandboxing
-        ProtectSystem = "strict";
-        ProtectHome = true;
-        PrivateTmp = true;
-        PrivateDevices = true;
-        PrivateIPC = true;
-        PrivateUsers = true;
-        ProtectHostname = true;
-        ProtectClock = true;
-        ProtectKernelTunables = true;
-        ProtectKernelModules = true;
-        ProtectKernelLogs = true;
-        ProtectControlGroups = true;
-        RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
-        RestrictNamespaces = true;
-        LockPersonality = true;
-        MemoryDenyWriteExecute  = false;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        RemoveIPC = true;
-        PrivateMounts = true;
-        # System Call Filtering
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @resources @privileged @setuid" "@chown" ];
-      };
-    };
-
-    users.users = lib.mkMerge [
-      (lib.mkIf (cfg.user == "nifi") {
-        nifi = {
-          group = cfg.group;
-          isSystemUser = true;
-          home = cfg.package;
-        };
-      })
-      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package nifiEnv ])
-    ];
-
-    users.groups = lib.optionalAttrs (cfg.group == "nifi") {
-      nifi = { };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/node-red.nix b/nixos/modules/services/web-apps/node-red.nix
deleted file mode 100644
index 4c095ea79bbd..000000000000
--- a/nixos/modules/services/web-apps/node-red.nix
+++ /dev/null
@@ -1,135 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.node-red;
-  defaultUser = "node-red";
-in
-{
-  options.services.node-red = {
-    enable = mkEnableOption "the Node-RED service";
-
-    package = mkPackageOption pkgs [ "nodePackages" "node-red" ] { };
-
-    openFirewall = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Open ports in the firewall for the server.
-      '';
-    };
-
-    withNpmAndGcc = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Give Node-RED access to NPM and GCC at runtime, so 'Nodes' can be
-        downloaded and managed imperatively via the 'Palette Manager'.
-      '';
-    };
-
-    configFile = mkOption {
-      type = types.path;
-      default = "${cfg.package}/lib/node_modules/node-red/settings.js";
-      defaultText = literalExpression ''"''${package}/lib/node_modules/node-red/settings.js"'';
-      description = ''
-        Path to the JavaScript configuration file.
-        See <https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js>
-        for a configuration example.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 1880;
-      description = "Listening port.";
-    };
-
-    user = mkOption {
-      type = types.str;
-      default = defaultUser;
-      description = ''
-        User under which Node-RED runs.If left as the default value this user
-        will automatically be created on system activation, otherwise the
-        sysadmin is responsible for ensuring the user exists.
-      '';
-    };
-
-    group = mkOption {
-      type = types.str;
-      default = defaultUser;
-      description = ''
-        Group under which Node-RED runs.If left as the default value this group
-        will automatically be created on system activation, otherwise the
-        sysadmin is responsible for ensuring the group exists.
-      '';
-    };
-
-    userDir = mkOption {
-      type = types.path;
-      default = "/var/lib/node-red";
-      description = ''
-        The directory to store all user data, such as flow and credential files and all library data. If left
-        as the default value this directory will automatically be created before the node-red service starts,
-        otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership
-        and permissions.
-      '';
-    };
-
-    safe = mkOption {
-      type = types.bool;
-      default = false;
-      description = "Whether to launch Node-RED in --safe mode.";
-    };
-
-    define = mkOption {
-      type = types.attrs;
-      default = {};
-      description = "List of settings.js overrides to pass via -D to Node-RED.";
-      example = literalExpression ''
-        {
-          "logging.console.level" = "trace";
-        }
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    users.users = optionalAttrs (cfg.user == defaultUser) {
-      ${defaultUser} = {
-        isSystemUser = true;
-        group = defaultUser;
-      };
-    };
-
-    users.groups = optionalAttrs (cfg.group == defaultUser) {
-      ${defaultUser} = { };
-    };
-
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.port ];
-    };
-
-    systemd.services.node-red = {
-      description = "Node-RED Service";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "networking.target" ];
-      environment = {
-        HOME = cfg.userDir;
-      };
-      path = lib.optionals cfg.withNpmAndGcc [ pkgs.nodePackages.npm pkgs.gcc ];
-      serviceConfig = mkMerge [
-        {
-          User = cfg.user;
-          Group = cfg.group;
-          ExecStart = "${cfg.package}/bin/node-red ${pkgs.lib.optionalString cfg.safe "--safe"} --settings ${cfg.configFile} --port ${toString cfg.port} --userDir ${cfg.userDir} ${concatStringsSep " " (mapAttrsToList (name: value: "-D ${name}=${value}") cfg.define)}";
-          PrivateTmp = true;
-          Restart = "always";
-          WorkingDirectory = cfg.userDir;
-        }
-        (mkIf (cfg.userDir == "/var/lib/node-red") { StateDirectory = "node-red"; })
-      ];
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/ocis.md b/nixos/modules/services/web-apps/ocis.md
deleted file mode 100644
index 9156e927ed2d..000000000000
--- a/nixos/modules/services/web-apps/ocis.md
+++ /dev/null
@@ -1,113 +0,0 @@
-# ownCloud Infinite Scale {#module-services-ocis}
-
-[ownCloud Infinite Scale](https://owncloud.dev/ocis/) (oCIS) is an open-source,
-modern file-sync and sharing platform. It is a ground-up rewrite of the well-known PHP based ownCloud server.
-
-The server setup can be automated using
-[services.ocis](#opt-services.ocis.enable). The desktop client is packaged at
-`pkgs.owncloud-client`.
-
-## Basic usage {#module-services-ocis-basic-usage}
-
-oCIS is a golang application and does not require an HTTP server (such as nginx)
-in front of it, though you may optionally use one if you will.
-
-oCIS is configured using a combination of yaml and environment variables. It is
-recommended to familiarize yourself with upstream's available configuration
-options and deployment instructions:
-
-* [Getting Started](https://owncloud.dev/ocis/getting-started/)
-* [Configuration](https://owncloud.dev/ocis/config/)
-* [Basic Setup](https://owncloud.dev/ocis/deployment/basic-remote-setup/)
-
-A very basic configuration may look like this:
-```
-{ pkgs, ... }:
-{
-  services.ocis = {
-    enable = true;
-    configDir = "/etc/ocis/config";
-  };
-}
-```
-
-This will start the oCIS server and make it available at `https://localhost:9200`
-
-However to make this configuration work you will need generate a configuration.
-You can do this with:
-
-```console
-$ nix-shell -p ocis-bin
-$ mkdir scratch/
-$ cd scratch/
-$ ocis init --config-path . --admin-password "changeme"
-```
-
-You may need to pass `--insecure true` or provide the `OCIS_INSECURE = true;` to
-[`services.ocis.environment`][mod-envFile], if TLS certificates are generated
-and managed externally (e.g. if you are using oCIS behind reverse proxy).
-
-If you want to manage the config file in your nix configuration, then it is
-encouraged to use a secrets manager like sops-nix or agenix.
-
-Be careful not to write files containing secrets to the globally readable nix
-store.
-
-Please note that current NixOS module for oCIS is configured to run in `fullstack`
-mode, which starts all the services for owncloud on single instance. This will
-start multiple ocis services and listen on multiple other ports.
-
-Current known services and their ports are as below:
-
-| Service            | Group   |  Port |
-|--------------------|---------|-------|
-| gateway            | api     |  9142 |
-| sharing            | api     |  9150 |
-| app-registry       | api     |  9242 |
-| ocdav              | web     | 45023 |
-| auth-machine       | api     |  9166 |
-| storage-system     | api     |  9215 |
-| webdav             | web     |  9115 |
-| webfinger          | web     | 46871 |
-| storage-system     | web     |  9216 |
-| web                | web     |  9100 |
-| eventhistory       | api     | 33177 |
-| ocs                | web     |  9110 |
-| storage-publiclink | api     |  9178 |
-| settings           | web     |  9190 |
-| ocm                | api     |  9282 |
-| settings           | api     |  9191 |
-| ocm                | web     |  9280 |
-| app-provider       | api     |  9164 |
-| storage-users      | api     |  9157 |
-| auth-service       | api     |  9199 |
-| thumbnails         | web     |  9186 |
-| thumbnails         | api     |  9185 |
-| storage-shares     | api     |  9154 |
-| sse                | sse     | 46833 |
-| userlog            | userlog | 45363 |
-| search             | api     |  9220 |
-| proxy              | web     |  9200 |
-| idp                | web     |  9130 |
-| frontend           | web     |  9140 |
-| groups             | api     |  9160 |
-| graph              | graph   |  9120 |
-| users              | api     |  9144 |
-| auth-basic         | api     |  9146 |
-
-## Configuration via environment variables
-
-You can also eschew the config file entirely and pass everything to oCIS via
-environment variables. For this make use of
-[`services.ocis.environment`][mod-env] for non-sensitive
-values, and
-[`services.ocis.environmentFile`][mod-envFile] for
-sensitive values.
-
-Configuration in (`services.ocis.environment`)[mod-env] overrides those from
-[`services.ocis.environmentFile`][mod-envFile] and will have highest
-precedence
-
-
-[mod-env]: #opt-services.ocis.environment
-[mod-envFile]: #opt-services.ocis.environmentFile
diff --git a/nixos/modules/services/web-apps/ocis.nix b/nixos/modules/services/web-apps/ocis.nix
deleted file mode 100644
index 0266eb6ad29c..000000000000
--- a/nixos/modules/services/web-apps/ocis.nix
+++ /dev/null
@@ -1,201 +0,0 @@
-{
-  config,
-  lib,
-  pkgs,
-  ...
-}:
-
-let
-  inherit (lib) types;
-  cfg = config.services.ocis;
-  defaultUser = "ocis";
-  defaultGroup = defaultUser;
-in
-{
-  options = {
-    services.ocis = {
-      enable = lib.mkEnableOption "ownCloud Infinite Scale";
-
-      package = lib.mkPackageOption pkgs "ocis-bin" { };
-
-      configDir = lib.mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        example = "/var/lib/ocis/config";
-        description = ''
-          Path to directory containing oCIS config file.
-
-          Example config can be generated by `ocis init --config-path fileName --admin-password "adminPass"`.
-          Add `--insecure true` if SSL certificates are generated and managed externally (e.g. using oCIS behind reverse proxy).
-
-          Note: This directory must contain at least a `ocis.yaml`. Ensure
-          [user](#opt-services.ocis.user) has read/write access to it. In some
-          circumstances you may need to add additional oCIS configuration files (e.g.,
-          `proxy.yaml`) to this directory.
-        '';
-      };
-
-      environmentFile = lib.mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        example = "/run/keys/ocis.env";
-        description = ''
-          An environment file as defined in {manpage}`systemd.exec(5)`.
-
-          Configuration provided in this file will override those from [configDir](#opt-services.ocis.configDir)/ocis.yaml.
-        '';
-      };
-
-      user = lib.mkOption {
-        type = types.str;
-        default = defaultUser;
-        example = "yourUser";
-        description = ''
-          The user to run oCIS as.
-          By default, a user named `${defaultUser}` will be created whose home
-          directory is [stateDir](#opt-services.ocis.stateDir).
-        '';
-      };
-
-      group = lib.mkOption {
-        type = types.str;
-        default = defaultGroup;
-        example = "yourGroup";
-        description = ''
-          The group to run oCIS under.
-          By default, a group named `${defaultGroup}` will be created.
-        '';
-      };
-
-      address = lib.mkOption {
-        type = types.str;
-        default = "127.0.0.1";
-        description = "Web interface address.";
-      };
-
-      port = lib.mkOption {
-        type = types.port;
-        default = 9200;
-        description = "Web interface port.";
-      };
-
-      url = lib.mkOption {
-        type = types.str;
-        default = "https://localhost:9200";
-        example = "https://some-hostname-or-ip:9200";
-        description = "Web interface address.";
-      };
-
-      stateDir = lib.mkOption {
-        default = "/var/lib/ocis";
-        type = types.str;
-        description = "ownCloud data directory.";
-      };
-
-      environment = lib.mkOption {
-        type = types.attrsOf types.str;
-        default = { };
-        description = ''
-          Extra config options.
-
-          See [the documentation](https://doc.owncloud.com/ocis/next/deployment/services/services.html) for available options.
-          See [notes for environment variables](https://doc.owncloud.com/ocis/next/deployment/services/env-var-note.html) for more information.
-
-          Note that all the attributes here will be copied to /nix/store/ and will be world readable. Options like *_PASSWORD or *_SECRET should be part of     [environmentFile](#opt-services.ocis.environmentFile) instead, and are only provided here for illustrative purpose.
-
-          Configuration here will override those from [environmentFile](#opt-services.ocis.environmentFile) and will have highest precedence, at the cost of security. Do NOT put security sensitive stuff here.
-        '';
-        example = {
-          OCIS_INSECURE = "false";
-          OCIS_LOG_LEVEL = "error";
-          OCIS_JWT_SECRET = "super_secret";
-          OCIS_TRANSFER_SECRET = "foo";
-          OCIS_MACHINE_AUTH_API_KEY = "foo";
-          OCIS_SYSTEM_USER_ID = "123";
-          OCIS_MOUNT_ID = "123";
-          OCIS_STORAGE_USERS_MOUNT_ID = "123";
-          GATEWAY_STORAGE_USERS_MOUNT_ID = "123";
-          CS3_ALLOW_INSECURE = "true";
-          OCIS_INSECURE_BACKENDS = "true";
-          TLS_INSECURE = "true";
-          TLS_SKIP_VERIFY_CLIENT_CERT = "true";
-          WEBDAV_ALLOW_INSECURE = "true";
-          IDP_TLS = "false";
-          GRAPH_APPLICATION_ID = "1234";
-          IDM_IDPSVC_PASSWORD = "password";
-          IDM_REVASVC_PASSWORD = "password";
-          IDM_SVC_PASSWORD = "password";
-          IDP_ISS = "https://localhost:9200";
-          OCIS_LDAP_BIND_PASSWORD = "password";
-          OCIS_SERVICE_ACCOUNT_ID = "foo";
-          OCIS_SERVICE_ACCOUNT_SECRET = "foo";
-          OCIS_SYSTEM_USER_API_KEY = "foo";
-          STORAGE_USERS_MOUNT_ID = "123";
-        };
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    users.users.${defaultUser} = lib.mkIf (cfg.user == defaultUser) {
-      group = cfg.group;
-      home = cfg.stateDir;
-      isSystemUser = true;
-      createHome = true;
-      description = "ownCloud Infinite Scale daemon user";
-    };
-
-    users.groups = lib.mkIf (cfg.group == defaultGroup) { ${defaultGroup} = { }; };
-
-    systemd = {
-      services.ocis = {
-        description = "ownCloud Infinite Scale Stack";
-        wantedBy = [ "multi-user.target" ];
-        environment = {
-          PROXY_HTTP_ADDR = "${cfg.address}:${toString cfg.port}";
-          OCIS_URL = cfg.url;
-          OCIS_CONFIG_DIR = if (cfg.configDir == null) then "${cfg.stateDir}/config" else cfg.configDir;
-          OCIS_BASE_DATA_PATH = cfg.stateDir;
-        } // cfg.environment;
-        serviceConfig = {
-          Type = "simple";
-          ExecStart = "${lib.getExe cfg.package} server";
-          WorkingDirectory = cfg.stateDir;
-          User = cfg.user;
-          Group = cfg.group;
-          Restart = "always";
-          EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
-          ReadWritePaths = [ cfg.stateDir ];
-          ReadOnlyPaths = [ cfg.configDir ];
-          MemoryDenyWriteExecute = true;
-          NoNewPrivileges = true;
-          PrivateTmp = true;
-          PrivateDevices = true;
-          ProtectSystem = "strict";
-          ProtectHome = true;
-          ProtectControlGroups = true;
-          ProtectKernelModules = true;
-          ProtectKernelTunables = true;
-          ProtectKernelLogs = true;
-          RestrictAddressFamilies = [
-            "AF_UNIX"
-            "AF_INET"
-            "AF_INET6"
-            "AF_NETLINK"
-          ];
-          RestrictNamespaces = true;
-          RestrictRealtime = true;
-          RestrictSUIDSGID = true;
-          LockPersonality = true;
-          SystemCallArchitectures = "native";
-        };
-      };
-    };
-  };
-
-  meta.maintainers = with lib.maintainers; [
-    bhankas
-    danth
-    ramblurr
-  ];
-}
diff --git a/nixos/modules/services/web-apps/onlyoffice.nix b/nixos/modules/services/web-apps/onlyoffice.nix
deleted file mode 100644
index 545ca68ccaac..000000000000
--- a/nixos/modules/services/web-apps/onlyoffice.nix
+++ /dev/null
@@ -1,291 +0,0 @@
-{ lib, config, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.onlyoffice;
-in
-{
-  options.services.onlyoffice = {
-    enable = mkEnableOption "OnlyOffice DocumentServer";
-
-    enableExampleServer = mkEnableOption "OnlyOffice example server";
-
-    hostname = mkOption {
-      type = types.str;
-      default = "localhost";
-      description = "FQDN for the onlyoffice instance.";
-    };
-
-    jwtSecretFile = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      description = ''
-        Path to a file that contains the secret to sign web requests using JSON Web Tokens.
-        If left at the default value null signing is disabled.
-      '';
-    };
-
-    package = mkPackageOption pkgs "onlyoffice-documentserver" { };
-
-    port = mkOption {
-      type = types.port;
-      default = 8000;
-      description = "Port the OnlyOffice DocumentServer should listens on.";
-    };
-
-    examplePort = mkOption {
-      type = types.port;
-      default = null;
-      description = "Port the OnlyOffice Example server should listens on.";
-    };
-
-    postgresHost = mkOption {
-      type = types.str;
-      default = "/run/postgresql";
-      description = "The Postgresql hostname or socket path OnlyOffice should connect to.";
-    };
-
-    postgresName = mkOption {
-      type = types.str;
-      default = "onlyoffice";
-      description = "The name of database OnlyOffice should user.";
-    };
-
-    postgresPasswordFile = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      description = ''
-        Path to a file that contains the password OnlyOffice should use to connect to Postgresql.
-        Unused when using socket authentication.
-      '';
-    };
-
-    postgresUser = mkOption {
-      type = types.str;
-      default = "onlyoffice";
-      description = ''
-        The username OnlyOffice should use to connect to Postgresql.
-        Unused when using socket authentication.
-      '';
-    };
-
-    rabbitmqUrl = mkOption {
-      type = types.str;
-      default = "amqp://guest:guest@localhost:5672";
-      description = "The Rabbitmq in amqp URI style OnlyOffice should connect to.";
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    services = {
-      nginx = {
-        enable = mkDefault true;
-        # misses text/csv, font/ttf, application/x-font-ttf, application/rtf, application/wasm
-        recommendedGzipSettings = mkDefault true;
-        recommendedProxySettings = mkDefault true;
-
-        upstreams = {
-          # /etc/nginx/includes/http-common.conf
-          onlyoffice-docservice = {
-            servers = { "localhost:${toString cfg.port}" = { }; };
-          };
-          onlyoffice-example = lib.mkIf cfg.enableExampleServer {
-            servers = { "localhost:${toString cfg.examplePort}" = { }; };
-          };
-        };
-
-        virtualHosts.${cfg.hostname} = {
-          locations = {
-            # /etc/nginx/includes/ds-docservice.conf
-            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(web-apps\/apps\/api\/documents\/api\.js)$".extraConfig = ''
-              expires -1;
-              alias ${cfg.package}/var/www/onlyoffice/documentserver/$2;
-            '';
-            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(web-apps)(\/.*\.json)$".extraConfig = ''
-              expires 365d;
-              error_log /dev/null crit;
-              alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
-            '';
-            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(sdkjs-plugins)(\/.*\.json)$".extraConfig = ''
-              expires 365d;
-              error_log /dev/null crit;
-              alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
-            '';
-            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(web-apps|sdkjs|sdkjs-plugins|fonts)(\/.*)$".extraConfig = ''
-              expires 365d;
-              alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
-            '';
-            "~* ^(\/cache\/files.*)(\/.*)".extraConfig = ''
-              alias /var/lib/onlyoffice/documentserver/App_Data$1;
-              add_header Content-Disposition "attachment; filename*=UTF-8''$arg_filename";
-
-              set $secret_string verysecretstring;
-              secure_link $arg_md5,$arg_expires;
-              secure_link_md5 "$secure_link_expires$uri$secret_string";
-
-              if ($secure_link = "") {
-                return 403;
-              }
-
-              if ($secure_link = "0") {
-                return 410;
-              }
-            '';
-            "~* ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(internal)(\/.*)$".extraConfig = ''
-              allow 127.0.0.1;
-              deny all;
-              proxy_pass http://onlyoffice-docservice/$2$3;
-            '';
-            "~* ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(info)(\/.*)$".extraConfig = ''
-              allow 127.0.0.1;
-              deny all;
-              proxy_pass http://onlyoffice-docservice/$2$3;
-            '';
-            "/".extraConfig = ''
-              proxy_pass http://onlyoffice-docservice;
-            '';
-            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?(\/doc\/.*)".extraConfig = ''
-              proxy_pass http://onlyoffice-docservice$2;
-              proxy_http_version 1.1;
-            '';
-            "/${cfg.package.version}/".extraConfig = ''
-              proxy_pass http://onlyoffice-docservice/;
-            '';
-            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(dictionaries)(\/.*)$".extraConfig = ''
-              expires 365d;
-              alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
-            '';
-            # /etc/nginx/includes/ds-example.conf
-            "~ ^(\/welcome\/.*)$".extraConfig = ''
-              expires 365d;
-              alias ${cfg.package}/var/www/onlyoffice/documentserver-example$1;
-              index docker.html;
-            '';
-            "/example/".extraConfig = lib.mkIf cfg.enableExampleServer ''
-              proxy_pass http://onlyoffice-example/;
-              proxy_set_header X-Forwarded-Path /example;
-            '';
-          };
-          extraConfig = ''
-            rewrite ^/$ /welcome/ redirect;
-            rewrite ^\/OfficeWeb(\/apps\/.*)$ /${cfg.package.version}/web-apps$1 redirect;
-            rewrite ^(\/web-apps\/apps\/(?!api\/).*)$ /${cfg.package.version}$1 redirect;
-
-            # based on https://github.com/ONLYOFFICE/document-server-package/blob/master/common/documentserver/nginx/includes/http-common.conf.m4#L29-L34
-            # without variable indirection and correct variable names
-            proxy_set_header Host $host;
-            proxy_set_header X-Forwarded-Host $host;
-            proxy_set_header X-Forwarded-Proto $scheme;
-            # required for CSP to take effect
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-            # required for websocket
-            proxy_set_header Upgrade $http_upgrade;
-            proxy_set_header Connection $connection_upgrade;
-          '';
-        };
-      };
-
-      rabbitmq.enable = lib.mkDefault true;
-
-      postgresql = {
-        enable = lib.mkDefault true;
-        ensureDatabases = [ "onlyoffice" ];
-        ensureUsers = [{
-          name = "onlyoffice";
-          ensureDBOwnership = true;
-        }];
-      };
-    };
-
-    systemd.services = {
-      onlyoffice-converter = {
-        description = "onlyoffice converter";
-        after = [ "network.target" "onlyoffice-docservice.service" "postgresql.service" ];
-        requires = [ "network.target" "onlyoffice-docservice.service" "postgresql.service" ];
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper FileConverter/converter /run/onlyoffice/config";
-          Group = "onlyoffice";
-          Restart = "always";
-          RuntimeDirectory = "onlyoffice";
-          StateDirectory = "onlyoffice";
-          Type = "simple";
-          User = "onlyoffice";
-        };
-      };
-
-      onlyoffice-docservice =
-        let
-          onlyoffice-prestart = pkgs.writeShellScript "onlyoffice-prestart" ''
-            PATH=$PATH:${lib.makeBinPath (with pkgs; [ jq moreutils config.services.postgresql.package ])}
-            umask 077
-            mkdir -p /run/onlyoffice/config/ /var/lib/onlyoffice/documentserver/sdkjs/{slide/themes,common}/ /var/lib/onlyoffice/documentserver/{fonts,server/FileConverter/bin}/
-            cp -r ${cfg.package}/etc/onlyoffice/documentserver/* /run/onlyoffice/config/
-            chmod u+w /run/onlyoffice/config/default.json
-
-            # Allow members of the onlyoffice group to serve files under /var/lib/onlyoffice/documentserver/App_Data
-            chmod g+x /var/lib/onlyoffice/documentserver
-
-            cp /run/onlyoffice/config/default.json{,.orig}
-
-            # for a mapping of environment variables from the docker container to json options see
-            # https://github.com/ONLYOFFICE/Docker-DocumentServer/blob/master/run-document-server.sh
-            jq '
-              .services.CoAuthoring.server.port = ${toString cfg.port} |
-              .services.CoAuthoring.sql.dbHost = "${cfg.postgresHost}" |
-              .services.CoAuthoring.sql.dbName = "${cfg.postgresName}" |
-            ${lib.optionalString (cfg.postgresPasswordFile != null) ''
-              .services.CoAuthoring.sql.dbPass = "'"$(cat ${cfg.postgresPasswordFile})"'" |
-            ''}
-              .services.CoAuthoring.sql.dbUser = "${cfg.postgresUser}" |
-            ${lib.optionalString (cfg.jwtSecretFile != null) ''
-              .services.CoAuthoring.token.enable.browser = true |
-              .services.CoAuthoring.token.enable.request.inbox = true |
-              .services.CoAuthoring.token.enable.request.outbox = true |
-              .services.CoAuthoring.secret.inbox.string = "'"$(cat ${cfg.jwtSecretFile})"'" |
-              .services.CoAuthoring.secret.outbox.string = "'"$(cat ${cfg.jwtSecretFile})"'" |
-              .services.CoAuthoring.secret.session.string = "'"$(cat ${cfg.jwtSecretFile})"'" |
-            ''}
-              .rabbitmq.url = "${cfg.rabbitmqUrl}"
-              ' /run/onlyoffice/config/default.json | sponge /run/onlyoffice/config/default.json
-
-            if psql -d onlyoffice -c "SELECT 'task_result'::regclass;" >/dev/null; then
-              psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/removetbl.sql
-              psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/createdb.sql
-            else
-              psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/createdb.sql
-            fi
-          '';
-        in
-        {
-          description = "onlyoffice documentserver";
-          after = [ "network.target" "postgresql.service" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-          serviceConfig = {
-            ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper DocService/docservice /run/onlyoffice/config";
-            ExecStartPre = [ onlyoffice-prestart ];
-            Group = "onlyoffice";
-            Restart = "always";
-            RuntimeDirectory = "onlyoffice";
-            StateDirectory = "onlyoffice";
-            Type = "simple";
-            User = "onlyoffice";
-          };
-        };
-    };
-
-    users.users = {
-      onlyoffice = {
-        description = "OnlyOffice Service";
-        group = "onlyoffice";
-        isSystemUser = true;
-      };
-
-      nginx.extraGroups = [ "onlyoffice" ];
-    };
-
-    users.groups.onlyoffice = { };
-  };
-}
diff --git a/nixos/modules/services/web-apps/openvscode-server.nix b/nixos/modules/services/web-apps/openvscode-server.nix
deleted file mode 100644
index b3c22cd43b78..000000000000
--- a/nixos/modules/services/web-apps/openvscode-server.nix
+++ /dev/null
@@ -1,213 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.openvscode-server;
-  defaultUser = "openvscode-server";
-  defaultGroup = defaultUser;
-in
-{
-  options = {
-    services.openvscode-server = {
-      enable = lib.mkEnableOption "openvscode-server";
-
-      package = lib.mkPackageOption pkgs "openvscode-server" { };
-
-      extraPackages = lib.mkOption {
-        default = [ ];
-        description = ''
-          Additional packages to add to the openvscode-server {env}`PATH`.
-        '';
-        example = lib.literalExpression "[ pkgs.go ]";
-        type = lib.types.listOf lib.types.package;
-      };
-
-      extraEnvironment = lib.mkOption {
-        type = lib.types.attrsOf lib.types.str;
-        description = ''
-          Additional environment variables to pass to openvscode-server.
-        '';
-        default = { };
-        example = { PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig"; };
-      };
-
-      extraArguments = lib.mkOption {
-        default = [ ];
-        description = ''
-          Additional arguments to pass to openvscode-server.
-        '';
-        example = lib.literalExpression ''[ "--log=info" ]'';
-        type = lib.types.listOf lib.types.str;
-      };
-
-      host = lib.mkOption {
-        default = "localhost";
-        description = ''
-          The host name or IP address the server should listen to.
-        '';
-        type = lib.types.str;
-      };
-
-      port = lib.mkOption {
-        default = 3000;
-        description = ''
-          The port the server should listen to. If 0 is passed a random free port is picked. If a range in the format num-num is passed, a free port from the range (end inclusive) is selected.
-        '';
-        type = lib.types.port;
-      };
-
-      user = lib.mkOption {
-        default = defaultUser;
-        example = "yourUser";
-        description = ''
-          The user to run openvscode-server as.
-          By default, a user named `${defaultUser}` will be created.
-        '';
-        type = lib.types.str;
-      };
-
-      group = lib.mkOption {
-        default = defaultGroup;
-        example = "yourGroup";
-        description = ''
-          The group to run openvscode-server under.
-          By default, a group named `${defaultGroup}` will be created.
-        '';
-        type = lib.types.str;
-      };
-
-      extraGroups = lib.mkOption {
-        default = [ ];
-        description = ''
-          An array of additional groups for the `${defaultUser}` user.
-        '';
-        example = [ "docker" ];
-        type = lib.types.listOf lib.types.str;
-      };
-
-      withoutConnectionToken = lib.mkOption {
-        default = false;
-        description = ''
-          Run without a connection token. Only use this if the connection is secured by other means.
-        '';
-        example = true;
-        type = lib.types.bool;
-      };
-
-      socketPath = lib.mkOption {
-        default = null;
-        example = "/run/openvscode/socket";
-        description = ''
-          The path to a socket file for the server to listen to.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      userDataDir = lib.mkOption {
-        default = null;
-        description = ''
-          Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      serverDataDir = lib.mkOption {
-        default = null;
-        description = ''
-          Specifies the directory that server data is kept in.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      extensionsDir = lib.mkOption {
-        default = null;
-        description = ''
-          Set the root path for extensions.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      telemetryLevel = lib.mkOption {
-        default = null;
-        example = "crash";
-        description = ''
-          Sets the initial telemetry level. Valid levels are: 'off', 'crash', 'error' and 'all'.
-        '';
-        type = lib.types.nullOr (lib.types.enum [ "off" "crash" "error" "all" ]);
-      };
-
-      connectionToken = lib.mkOption {
-        default = null;
-        example = "secret-token";
-        description = ''
-          A secret that must be included with all requests.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      connectionTokenFile = lib.mkOption {
-        default = null;
-        description = ''
-          Path to a file that contains the connection token.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.openvscode-server = {
-      description = "OpenVSCode server";
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network-online.target" ];
-      path = cfg.extraPackages;
-      environment = cfg.extraEnvironment;
-      serviceConfig = {
-        ExecStart = ''
-          ${lib.getExe cfg.package} \
-            --accept-server-license-terms \
-            --host=${cfg.host} \
-            --port=${toString cfg.port} \
-        '' + lib.optionalString (cfg.telemetryLevel != null) ''
-          --telemetry-level=${cfg.telemetryLevel} \
-        '' + lib.optionalString (cfg.withoutConnectionToken) ''
-          --without-connection-token \
-        '' + lib.optionalString (cfg.socketPath != null) ''
-          --socket-path=${cfg.socketPath} \
-        '' + lib.optionalString (cfg.userDataDir != null) ''
-          --user-data-dir=${cfg.userDataDir} \
-        '' + lib.optionalString (cfg.serverDataDir != null) ''
-          --server-data-dir=${cfg.serverDataDir} \
-        '' + lib.optionalString (cfg.extensionsDir != null) ''
-          --extensions-dir=${cfg.extensionsDir} \
-        '' + lib.optionalString (cfg.connectionToken != null) ''
-          --connection-token=${cfg.connectionToken} \
-        '' + lib.optionalString (cfg.connectionTokenFile != null) ''
-          --connection-token-file=${cfg.connectionTokenFile} \
-        '' + lib.escapeShellArgs cfg.extraArguments;
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        RuntimeDirectory = cfg.user;
-        User = cfg.user;
-        Group = cfg.group;
-        Restart = "on-failure";
-      };
-    };
-
-    users.users."${cfg.user}" = lib.mkMerge [
-      (lib.mkIf (cfg.user == defaultUser) {
-        isNormalUser = true;
-        description = "openvscode-server user";
-        inherit (cfg) group;
-      })
-      {
-        packages = cfg.extraPackages;
-        inherit (cfg) extraGroups;
-      }
-    ];
-
-    users.groups."${defaultGroup}" = lib.mkIf (cfg.group == defaultGroup) { };
-  };
-
-  meta.maintainers = [ lib.maintainers.drupol ];
-}
diff --git a/nixos/modules/services/web-apps/openwebrx.nix b/nixos/modules/services/web-apps/openwebrx.nix
deleted file mode 100644
index 614eb963b4a3..000000000000
--- a/nixos/modules/services/web-apps/openwebrx.nix
+++ /dev/null
@@ -1,33 +0,0 @@
-{ config, lib, pkgs, ... }:
-let
-  cfg = config.services.openwebrx;
-in
-{
-  options.services.openwebrx = with lib; {
-    enable = mkEnableOption "OpenWebRX Web interface for Software-Defined Radios on http://localhost:8073";
-
-    package = mkPackageOption pkgs "openwebrx" { };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.openwebrx = {
-      wantedBy = [ "multi-user.target" ];
-      path = with pkgs; [
-        csdr
-        digiham
-        codec2
-        js8call
-        m17-cxx-demod
-        alsaUtils
-        netcat
-      ];
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/openwebrx";
-        Restart = "always";
-        DynamicUser = true;
-        # openwebrx uses /var/lib/openwebrx by default
-        StateDirectory = [ "openwebrx" ];
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/outline.nix b/nixos/modules/services/web-apps/outline.nix
deleted file mode 100644
index 4c1de579ecc5..000000000000
--- a/nixos/modules/services/web-apps/outline.nix
+++ /dev/null
@@ -1,791 +0,0 @@
-{ config, lib, pkgs, ...}:
-
-let
-  defaultUser = "outline";
-  cfg = config.services.outline;
-  inherit (lib) mkRemovedOptionModule;
-in
-{
-  imports = [
-    (mkRemovedOptionModule [ "services" "outline" "sequelizeArguments" ] "Database migration are run agains configurated database by outline directly")
-  ];
-  # See here for a reference of all the options:
-  #   https://github.com/outline/outline/blob/v0.67.0/.env.sample
-  #   https://github.com/outline/outline/blob/v0.67.0/app.json
-  #   https://github.com/outline/outline/blob/v0.67.0/server/env.ts
-  #   https://github.com/outline/outline/blob/v0.67.0/shared/types.ts
-  # The order is kept the same here to make updating easier.
-  options.services.outline = {
-    enable = lib.mkEnableOption "outline";
-
-    package = lib.mkOption {
-      default = pkgs.outline;
-      defaultText = lib.literalExpression "pkgs.outline";
-      type = lib.types.package;
-      example = lib.literalExpression ''
-        pkgs.outline.overrideAttrs (super: {
-          # Ignore the domain part in emails that come from OIDC. This is might
-          # be helpful if you want multiple users with different email providers
-          # to still land in the same team. Note that this effectively makes
-          # Outline a single-team instance.
-          patchPhase = ${"''"}
-            sed -i 's/const domain = parts\.length && parts\[1\];/const domain = "example.com";/g' plugins/oidc/server/auth/oidc.ts
-          ${"''"};
-        })
-      '';
-      description = "Outline package to use.";
-    };
-
-    user = lib.mkOption {
-      type = lib.types.str;
-      default = defaultUser;
-      description = ''
-        User under which the service should run. If this is the default value,
-        the user will be created, with the specified group as the primary
-        group.
-      '';
-    };
-
-    group = lib.mkOption {
-      type = lib.types.str;
-      default = defaultUser;
-      description = ''
-        Group under which the service should run. If this is the default value,
-        the group will be created.
-      '';
-    };
-
-    #
-    # Required options
-    #
-
-    secretKeyFile = lib.mkOption {
-      type = lib.types.str;
-      default = "/var/lib/outline/secret_key";
-      description = ''
-        File path that contains the application secret key. It must be 32
-        bytes long and hex-encoded. If the file does not exist, a new key will
-        be generated and saved here.
-      '';
-    };
-
-    utilsSecretFile = lib.mkOption {
-      type = lib.types.str;
-      default = "/var/lib/outline/utils_secret";
-      description = ''
-        File path that contains the utility secret key. If the file does not
-        exist, a new key will be generated and saved here.
-      '';
-    };
-
-    databaseUrl = lib.mkOption {
-      type = lib.types.str;
-      default = "local";
-      description = ''
-        URI to use for the main PostgreSQL database. If this needs to include
-        credentials that shouldn't be world-readable in the Nix store, set an
-        environment file on the systemd service and override the
-        `DATABASE_URL` entry. Pass the string
-        `local` to setup a database on the local server.
-      '';
-    };
-
-    redisUrl = lib.mkOption {
-      type = lib.types.str;
-      default = "local";
-      description = ''
-        Connection to a redis server. If this needs to include credentials
-        that shouldn't be world-readable in the Nix store, set an environment
-        file on the systemd service and override the
-        `REDIS_URL` entry. Pass the string
-        `local` to setup a local Redis database.
-      '';
-    };
-
-    publicUrl = lib.mkOption {
-      type = lib.types.str;
-      default = "http://localhost:3000";
-      description = "The fully qualified, publicly accessible URL";
-    };
-
-    port = lib.mkOption {
-      type = lib.types.port;
-      default = 3000;
-      description = "Listening port.";
-    };
-
-    storage = lib.mkOption {
-      description = ''
-        To support uploading of images for avatars and document attachments an
-        s3-compatible storage can be provided. AWS S3 is recommended for
-        redundancy however if you want to keep all file storage local an
-        alternative such as [minio](https://github.com/minio/minio)
-        can be used.
-        Local filesystem storage can also be used.
-
-        A more detailed guide on setting up storage is available
-        [here](https://docs.getoutline.com/s/hosting/doc/file-storage-N4M0T6Ypu7).
-      '';
-      example = lib.literalExpression ''
-        {
-          accessKey = "...";
-          secretKeyFile = "/somewhere";
-          uploadBucketUrl = "https://minio.example.com";
-          uploadBucketName = "outline";
-          region = "us-east-1";
-        }
-      '';
-      type = lib.types.submodule {
-        options = {
-          storageType = lib.mkOption {
-            type = lib.types.enum [ "local" "s3" ];
-            description = "File storage type, it can be local or s3.";
-            default = "s3";
-          };
-          localRootDir = lib.mkOption {
-            type = lib.types.str;
-            description = ''
-              If `storageType` is `local`, this sets the parent directory
-              under which all attachments/images go.
-            '';
-            default = "/var/lib/outline/data";
-          };
-          accessKey = lib.mkOption {
-            type = lib.types.str;
-            description = "S3 access key.";
-          };
-          secretKeyFile = lib.mkOption {
-            type = lib.types.path;
-            description = "File path that contains the S3 secret key.";
-          };
-          region = lib.mkOption {
-            type = lib.types.str;
-            default = "xx-xxxx-x";
-            description = "AWS S3 region name.";
-          };
-          uploadBucketUrl = lib.mkOption {
-            type = lib.types.str;
-            description = ''
-              URL endpoint of an S3-compatible API where uploads should be
-              stored.
-            '';
-          };
-          uploadBucketName = lib.mkOption {
-            type = lib.types.str;
-            description = "Name of the bucket where uploads should be stored.";
-          };
-          uploadMaxSize = lib.mkOption {
-            type = lib.types.int;
-            default = 26214400;
-            description = "Maxmium file size for uploads.";
-          };
-          forcePathStyle = lib.mkOption {
-            type = lib.types.bool;
-            default = true;
-            description = "Force S3 path style.";
-          };
-          acl = lib.mkOption {
-            type = lib.types.str;
-            default = "private";
-            description = "ACL setting.";
-          };
-        };
-      };
-    };
-
-    #
-    # Authentication
-    #
-
-    slackAuthentication = lib.mkOption {
-      description = ''
-        To configure Slack auth, you'll need to create an Application at
-        https://api.slack.com/apps
-
-        When configuring the Client ID, add a redirect URL under "OAuth & Permissions"
-        to `https://[publicUrl]/auth/slack.callback`.
-      '';
-      default = null;
-      type = lib.types.nullOr (lib.types.submodule {
-        options = {
-          clientId = lib.mkOption {
-            type = lib.types.str;
-            description = "Authentication key.";
-          };
-          secretFile = lib.mkOption {
-            type = lib.types.str;
-            description = "File path containing the authentication secret.";
-          };
-        };
-      });
-    };
-
-    googleAuthentication = lib.mkOption {
-      description = ''
-        To configure Google auth, you'll need to create an OAuth Client ID at
-        https://console.cloud.google.com/apis/credentials
-
-        When configuring the Client ID, add an Authorized redirect URI to
-        `https://[publicUrl]/auth/google.callback`.
-      '';
-      default = null;
-      type = lib.types.nullOr (lib.types.submodule {
-        options = {
-          clientId = lib.mkOption {
-            type = lib.types.str;
-            description = "Authentication client identifier.";
-          };
-          clientSecretFile = lib.mkOption {
-            type = lib.types.str;
-            description = "File path containing the authentication secret.";
-          };
-        };
-      });
-    };
-
-    azureAuthentication = lib.mkOption {
-      description = ''
-        To configure Microsoft/Azure auth, you'll need to create an OAuth
-        Client. See
-        [the guide](https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4)
-        for details on setting up your Azure App.
-      '';
-      default = null;
-      type = lib.types.nullOr (lib.types.submodule {
-        options = {
-          clientId = lib.mkOption {
-            type = lib.types.str;
-            description = "Authentication client identifier.";
-          };
-          clientSecretFile = lib.mkOption {
-            type = lib.types.str;
-            description = "File path containing the authentication secret.";
-          };
-          resourceAppId = lib.mkOption {
-            type = lib.types.str;
-            description = "Authentication application resource ID.";
-          };
-        };
-      });
-    };
-
-    oidcAuthentication = lib.mkOption {
-      description = ''
-        To configure generic OIDC auth, you'll need some kind of identity
-        provider. See the documentation for whichever IdP you use to fill out
-        all the fields. The redirect URL is
-        `https://[publicUrl]/auth/oidc.callback`.
-      '';
-      default = null;
-      type = lib.types.nullOr (lib.types.submodule {
-        options = {
-          clientId = lib.mkOption {
-            type = lib.types.str;
-            description = "Authentication client identifier.";
-          };
-          clientSecretFile = lib.mkOption {
-            type = lib.types.str;
-            description = "File path containing the authentication secret.";
-          };
-          authUrl = lib.mkOption {
-            type = lib.types.str;
-            description = "OIDC authentication URL endpoint.";
-          };
-          tokenUrl = lib.mkOption {
-            type = lib.types.str;
-            description = "OIDC token URL endpoint.";
-          };
-          userinfoUrl = lib.mkOption {
-            type = lib.types.str;
-            description = "OIDC userinfo URL endpoint.";
-          };
-          usernameClaim = lib.mkOption {
-            type = lib.types.str;
-            description = ''
-              Specify which claims to derive user information from. Supports any
-              valid JSON path with the JWT payload
-            '';
-            default = "preferred_username";
-          };
-          displayName = lib.mkOption {
-            type = lib.types.str;
-            description = "Display name for OIDC authentication.";
-            default = "OpenID";
-          };
-          scopes = lib.mkOption {
-            type = lib.types.listOf lib.types.str;
-            description = "OpenID authentication scopes.";
-            default = [ "openid" "profile" "email" ];
-          };
-        };
-      });
-    };
-
-    #
-    # Optional configuration
-    #
-
-    sslKeyFile = lib.mkOption {
-      type = lib.types.nullOr lib.types.str;
-      default = null;
-      description = ''
-        File path that contains the Base64-encoded private key for HTTPS
-        termination. This is only required if you do not use an external reverse
-        proxy. See
-        [the documentation](https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4).
-      '';
-    };
-    sslCertFile = lib.mkOption {
-      type = lib.types.nullOr lib.types.str;
-      default = null;
-      description = ''
-        File path that contains the Base64-encoded certificate for HTTPS
-        termination. This is only required if you do not use an external reverse
-        proxy. See
-        [the documentation](https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4).
-      '';
-    };
-
-    cdnUrl = lib.mkOption {
-      type = lib.types.str;
-      default = "";
-      description = ''
-        If using a Cloudfront/Cloudflare distribution or similar it can be set
-        using this option. This will cause paths to JavaScript files,
-        stylesheets and images to be updated to the hostname defined here. In
-        your CDN configuration the origin server should be set to public URL.
-      '';
-    };
-
-    forceHttps = lib.mkOption {
-      type = lib.types.bool;
-      default = true;
-      description = ''
-        Auto-redirect to HTTPS in production. The default is
-        `true` but you may set this to `false`
-        if you can be sure that SSL is terminated at an external loadbalancer.
-      '';
-    };
-
-    enableUpdateCheck = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = ''
-        Have the installation check for updates by sending anonymized statistics
-        to the maintainers.
-      '';
-    };
-
-    concurrency = lib.mkOption {
-      type = lib.types.int;
-      default = 1;
-      description = ''
-        How many processes should be spawned. For a rough estimate, divide your
-        server's available memory by 512.
-      '';
-    };
-
-    maximumImportSize = lib.mkOption {
-      type = lib.types.int;
-      default = 5120000;
-      description = ''
-        The maximum size of document imports. Overriding this could be required
-        if you have especially large Word documents with embedded imagery.
-      '';
-    };
-
-    debugOutput = lib.mkOption {
-      type = lib.types.nullOr (lib.types.enum [ "http" ]);
-      default = null;
-      description = "Set this to `http` log HTTP requests.";
-    };
-
-    slackIntegration = lib.mkOption {
-      description = ''
-        For a complete Slack integration with search and posting to channels
-        this configuration is also needed. See here for details:
-        https://wiki.generaloutline.com/share/be25efd1-b3ef-4450-b8e5-c4a4fc11e02a
-      '';
-      default = null;
-      type = lib.types.nullOr (lib.types.submodule {
-        options = {
-          verificationTokenFile = lib.mkOption {
-            type = lib.types.str;
-            description = "File path containing the verification token.";
-          };
-          appId = lib.mkOption {
-            type = lib.types.str;
-            description = "Application ID.";
-          };
-          messageActions = lib.mkOption {
-            type = lib.types.bool;
-            default = true;
-            description = "Whether to enable message actions.";
-          };
-        };
-      });
-    };
-
-    googleAnalyticsId = lib.mkOption {
-      type = lib.types.nullOr lib.types.str;
-      default = null;
-      description = ''
-        Optionally enable Google Analytics to track page views in the knowledge
-        base.
-      '';
-    };
-
-    sentryDsn = lib.mkOption {
-      type = lib.types.nullOr lib.types.str;
-      default = null;
-      description = ''
-        Optionally enable [Sentry](https://sentry.io/) to
-        track errors and performance.
-      '';
-    };
-
-    sentryTunnel = lib.mkOption {
-      type = lib.types.nullOr lib.types.str;
-      default = null;
-      description = ''
-        Optionally add a
-        [Sentry proxy tunnel](https://docs.sentry.io/platforms/javascript/troubleshooting/#using-the-tunnel-option)
-        for bypassing ad blockers in the UI.
-      '';
-    };
-
-    logo = lib.mkOption {
-      type = lib.types.nullOr lib.types.str;
-      default = null;
-      description = ''
-        Custom logo displayed on the authentication screen. This will be scaled
-        to a height of 60px.
-      '';
-    };
-
-    smtp = lib.mkOption {
-      description = ''
-        To support sending outgoing transactional emails such as
-        "document updated" or "you've been invited" you'll need to provide
-        authentication for an SMTP server.
-      '';
-      default = null;
-      type = lib.types.nullOr (lib.types.submodule {
-        options = {
-          host = lib.mkOption {
-            type = lib.types.str;
-            description = "Host name or IP address of the SMTP server.";
-          };
-          port = lib.mkOption {
-            type = lib.types.port;
-            description = "TCP port of the SMTP server.";
-          };
-          username = lib.mkOption {
-            type = lib.types.str;
-            description = "Username to authenticate with.";
-          };
-          passwordFile = lib.mkOption {
-            type = lib.types.str;
-            description = ''
-              File path containing the password to authenticate with.
-            '';
-          };
-          fromEmail = lib.mkOption {
-            type = lib.types.str;
-            description = "Sender email in outgoing mail.";
-          };
-          replyEmail = lib.mkOption {
-            type = lib.types.str;
-            description = "Reply address in outgoing mail.";
-          };
-          tlsCiphers = lib.mkOption {
-            type = lib.types.str;
-            default = "";
-            description = "Override SMTP cipher configuration.";
-          };
-          secure = lib.mkOption {
-            type = lib.types.bool;
-            default = true;
-            description = "Use a secure SMTP connection.";
-          };
-        };
-      });
-    };
-
-    defaultLanguage = lib.mkOption {
-      type = lib.types.enum [
-         "da_DK"
-         "de_DE"
-         "en_US"
-         "es_ES"
-         "fa_IR"
-         "fr_FR"
-         "it_IT"
-         "ja_JP"
-         "ko_KR"
-         "nl_NL"
-         "pl_PL"
-         "pt_BR"
-         "pt_PT"
-         "ru_RU"
-         "sv_SE"
-         "th_TH"
-         "vi_VN"
-         "zh_CN"
-         "zh_TW"
-      ];
-      default = "en_US";
-      description = ''
-        The default interface language. See
-        [translate.getoutline.com](https://translate.getoutline.com/)
-        for a list of available language codes and their rough percentage
-        translated.
-      '';
-    };
-
-    rateLimiter.enable = lib.mkEnableOption "rate limiter for the application web server";
-    rateLimiter.requests = lib.mkOption {
-      type = lib.types.int;
-      default = 5000;
-      description = "Maximum number of requests in a throttling window.";
-    };
-    rateLimiter.durationWindow = lib.mkOption {
-      type = lib.types.int;
-      default = 60;
-      description = "Length of a throttling window.";
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    users.users = lib.optionalAttrs (cfg.user == defaultUser) {
-      ${defaultUser} = {
-        isSystemUser = true;
-        group = cfg.group;
-      };
-    };
-
-    users.groups = lib.optionalAttrs (cfg.group == defaultUser) {
-      ${defaultUser} = { };
-    };
-
-    systemd.tmpfiles.rules = [
-      "f ${cfg.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -"
-      "f ${cfg.utilsSecretFile} 0600 ${cfg.user} ${cfg.group} -"
-      (if (cfg.storage.storageType == "s3") then
-        "f ${cfg.storage.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -"
-      else
-        "d ${cfg.storage.localRootDir} 0700 ${cfg.user} ${cfg.group} - -")
-    ];
-
-    services.postgresql = lib.mkIf (cfg.databaseUrl == "local") {
-      enable = true;
-      ensureUsers = [{
-        name = "outline";
-        ensureDBOwnership = true;
-      }];
-      ensureDatabases = [ "outline" ];
-    };
-
-    # Outline is unable to create the uuid-ossp extension when using postgresql 12, in later version this
-    # extension can be created without superuser permission. This services therefor this extension before
-    # outline starts and postgresql 12 is using on the host.
-    #
-    # Can be removed after postgresql 12 is dropped from nixos.
-    systemd.services.outline-postgresql =
-      let
-        pgsql = config.services.postgresql;
-      in
-        lib.mkIf (cfg.databaseUrl == "local" && pgsql.package == pkgs.postgresql_12) {
-          after = [ "postgresql.service" ];
-          bindsTo = [ "postgresql.service" ];
-          wantedBy = [ "outline.service" ];
-          partOf = [ "outline.service" ];
-          path = [
-            pgsql.package
-          ];
-          script = ''
-            set -o errexit -o pipefail -o nounset -o errtrace
-            shopt -s inherit_errexit
-
-            psql outline -tAc 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
-          '';
-
-          serviceConfig = {
-            User = pgsql.superUser;
-            Type = "oneshot";
-            RemainAfterExit = true;
-          };
-        };
-
-    services.redis.servers.outline = lib.mkIf (cfg.redisUrl == "local") {
-      enable = true;
-      user = config.services.outline.user;
-      port = 0; # Disable the TCP listener
-    };
-
-    systemd.services.outline = let
-      localRedisUrl = "redis+unix:///run/redis-outline/redis.sock";
-      localPostgresqlUrl = "postgres://localhost/outline?host=/run/postgresql";
-    in {
-      description = "Outline wiki and knowledge base";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "networking.target" ]
-        ++ lib.optional (cfg.databaseUrl == "local") "postgresql.service"
-        ++ lib.optional (cfg.redisUrl == "local") "redis-outline.service";
-      requires = lib.optional (cfg.databaseUrl == "local") "postgresql.service"
-        ++ lib.optional (cfg.redisUrl == "local") "redis-outline.service";
-      path = [
-        pkgs.openssl # Required by the preStart script
-      ];
-
-
-      environment = lib.mkMerge [
-        {
-          NODE_ENV = "production";
-
-          REDIS_URL = if cfg.redisUrl == "local" then localRedisUrl else cfg.redisUrl;
-          URL = cfg.publicUrl;
-          PORT = builtins.toString cfg.port;
-
-          CDN_URL = cfg.cdnUrl;
-          FORCE_HTTPS = builtins.toString cfg.forceHttps;
-          ENABLE_UPDATES = builtins.toString cfg.enableUpdateCheck;
-          WEB_CONCURRENCY = builtins.toString cfg.concurrency;
-          MAXIMUM_IMPORT_SIZE = builtins.toString cfg.maximumImportSize;
-          DEBUG = cfg.debugOutput;
-          GOOGLE_ANALYTICS_ID = lib.optionalString (cfg.googleAnalyticsId != null) cfg.googleAnalyticsId;
-          SENTRY_DSN = lib.optionalString (cfg.sentryDsn != null) cfg.sentryDsn;
-          SENTRY_TUNNEL = lib.optionalString (cfg.sentryTunnel != null) cfg.sentryTunnel;
-          TEAM_LOGO = lib.optionalString (cfg.logo != null) cfg.logo;
-          DEFAULT_LANGUAGE = cfg.defaultLanguage;
-
-          RATE_LIMITER_ENABLED = builtins.toString cfg.rateLimiter.enable;
-          RATE_LIMITER_REQUESTS = builtins.toString cfg.rateLimiter.requests;
-          RATE_LIMITER_DURATION_WINDOW = builtins.toString cfg.rateLimiter.durationWindow;
-
-          FILE_STORAGE = cfg.storage.storageType;
-          FILE_STORAGE_UPLOAD_MAX_SIZE = builtins.toString cfg.storage.uploadMaxSize;
-          FILE_STORAGE_LOCAL_ROOT_DIR = cfg.storage.localRootDir;
-        }
-
-        (lib.mkIf (cfg.storage.storageType == "s3") {
-          AWS_ACCESS_KEY_ID = cfg.storage.accessKey;
-          AWS_REGION = cfg.storage.region;
-          AWS_S3_UPLOAD_BUCKET_URL = cfg.storage.uploadBucketUrl;
-          AWS_S3_UPLOAD_BUCKET_NAME = cfg.storage.uploadBucketName;
-          AWS_S3_FORCE_PATH_STYLE = builtins.toString cfg.storage.forcePathStyle;
-          AWS_S3_ACL = cfg.storage.acl;
-        })
-
-        (lib.mkIf (cfg.slackAuthentication != null) {
-          SLACK_CLIENT_ID = cfg.slackAuthentication.clientId;
-        })
-
-        (lib.mkIf (cfg.googleAuthentication != null) {
-          GOOGLE_CLIENT_ID = cfg.googleAuthentication.clientId;
-        })
-
-        (lib.mkIf (cfg.azureAuthentication != null) {
-          AZURE_CLIENT_ID = cfg.azureAuthentication.clientId;
-          AZURE_RESOURCE_APP_ID = cfg.azureAuthentication.resourceAppId;
-        })
-
-        (lib.mkIf (cfg.oidcAuthentication != null) {
-          OIDC_CLIENT_ID = cfg.oidcAuthentication.clientId;
-          OIDC_AUTH_URI = cfg.oidcAuthentication.authUrl;
-          OIDC_TOKEN_URI = cfg.oidcAuthentication.tokenUrl;
-          OIDC_USERINFO_URI = cfg.oidcAuthentication.userinfoUrl;
-          OIDC_USERNAME_CLAIM = cfg.oidcAuthentication.usernameClaim;
-          OIDC_DISPLAY_NAME = cfg.oidcAuthentication.displayName;
-          OIDC_SCOPES = lib.concatStringsSep " " cfg.oidcAuthentication.scopes;
-        })
-
-        (lib.mkIf (cfg.slackIntegration != null) {
-          SLACK_APP_ID = cfg.slackIntegration.appId;
-          SLACK_MESSAGE_ACTIONS = builtins.toString cfg.slackIntegration.messageActions;
-        })
-
-        (lib.mkIf (cfg.smtp != null) {
-          SMTP_HOST = cfg.smtp.host;
-          SMTP_PORT = builtins.toString cfg.smtp.port;
-          SMTP_USERNAME = cfg.smtp.username;
-          SMTP_FROM_EMAIL = cfg.smtp.fromEmail;
-          SMTP_REPLY_EMAIL = cfg.smtp.replyEmail;
-          SMTP_TLS_CIPHERS = cfg.smtp.tlsCiphers;
-          SMTP_SECURE = builtins.toString cfg.smtp.secure;
-        })
-      ];
-
-      preStart = ''
-        if [ ! -s ${lib.escapeShellArg cfg.secretKeyFile} ]; then
-          openssl rand -hex 32 > ${lib.escapeShellArg cfg.secretKeyFile}
-        fi
-        if [ ! -s ${lib.escapeShellArg cfg.utilsSecretFile} ]; then
-          openssl rand -hex 32 > ${lib.escapeShellArg cfg.utilsSecretFile}
-        fi
-
-      '';
-
-      script = ''
-        export SECRET_KEY="$(head -n1 ${lib.escapeShellArg cfg.secretKeyFile})"
-        export UTILS_SECRET="$(head -n1 ${lib.escapeShellArg cfg.utilsSecretFile})"
-        ${lib.optionalString (cfg.storage.storageType == "s3") ''
-          export AWS_SECRET_ACCESS_KEY="$(head -n1 ${lib.escapeShellArg cfg.storage.secretKeyFile})"
-        ''}
-        ${lib.optionalString (cfg.slackAuthentication != null) ''
-          export SLACK_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.slackAuthentication.secretFile})"
-        ''}
-        ${lib.optionalString (cfg.googleAuthentication != null) ''
-          export GOOGLE_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.googleAuthentication.clientSecretFile})"
-        ''}
-        ${lib.optionalString (cfg.azureAuthentication != null) ''
-          export AZURE_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.azureAuthentication.clientSecretFile})"
-        ''}
-        ${lib.optionalString (cfg.oidcAuthentication != null) ''
-          export OIDC_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.oidcAuthentication.clientSecretFile})"
-        ''}
-        ${lib.optionalString (cfg.sslKeyFile != null) ''
-          export SSL_KEY="$(head -n1 ${lib.escapeShellArg cfg.sslKeyFile})"
-        ''}
-        ${lib.optionalString (cfg.sslCertFile != null) ''
-          export SSL_CERT="$(head -n1 ${lib.escapeShellArg cfg.sslCertFile})"
-        ''}
-        ${lib.optionalString (cfg.slackIntegration != null) ''
-          export SLACK_VERIFICATION_TOKEN="$(head -n1 ${lib.escapeShellArg cfg.slackIntegration.verificationTokenFile})"
-        ''}
-        ${lib.optionalString (cfg.smtp != null) ''
-          export SMTP_PASSWORD="$(head -n1 ${lib.escapeShellArg cfg.smtp.passwordFile})"
-        ''}
-
-        ${if (cfg.databaseUrl == "local") then ''
-          export DATABASE_URL=${lib.escapeShellArg localPostgresqlUrl}
-          export PGSSLMODE=disable
-        '' else ''
-          export DATABASE_URL=${lib.escapeShellArg cfg.databaseUrl}
-        ''}
-
-        ${cfg.package}/bin/outline-server
-      '';
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        Restart = "always";
-        ProtectSystem = "strict";
-        PrivateHome = true;
-        PrivateTmp = true;
-        UMask = "0007";
-
-        StateDirectory = "outline";
-        StateDirectoryMode = "0750";
-        RuntimeDirectory = "outline";
-        RuntimeDirectoryMode = "0750";
-        # This working directory is required to find stuff like the set of
-        # onboarding files:
-        WorkingDirectory = "${cfg.package}/share/outline";
-        # In case this directory is not in /var/lib/outline, it needs to be made writable explicitly
-        ReadWritePaths = lib.mkIf (cfg.storage.storageType == "local") [ cfg.storage.localRootDir ];
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/peering-manager.nix b/nixos/modules/services/web-apps/peering-manager.nix
deleted file mode 100644
index c85cb76e5ea1..000000000000
--- a/nixos/modules/services/web-apps/peering-manager.nix
+++ /dev/null
@@ -1,344 +0,0 @@
-{ config, lib, pkgs, buildEnv, ... }:
-
-let
-  cfg = config.services.peering-manager;
-
-  pythonFmt = pkgs.formats.pythonVars {};
-  settingsFile = pythonFmt.generate "peering-manager-settings.py" cfg.settings;
-  extraConfigFile = pkgs.writeTextFile {
-    name = "peering-manager-extraConfig.py";
-    text = cfg.extraConfig;
-  };
-  configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ];
-
-  pkg = (pkgs.peering-manager.overrideAttrs (old: {
-    postInstall = ''
-      ln -s ${configFile} $out/opt/peering-manager/peering_manager/configuration.py
-    '' + lib.optionalString cfg.enableLdap ''
-      ln -s ${cfg.ldapConfigPath} $out/opt/peering-manager/peering_manager/ldap_config.py
-    '';
-  })).override {
-    inherit (cfg) plugins;
-  };
-  peeringManagerManageScript = pkgs.writeScriptBin "peering-manager-manage" ''
-    #!${pkgs.stdenv.shell}
-    export PYTHONPATH=${pkg.pythonPath}
-    sudo -u peering-manager ${pkg}/bin/peering-manager "$@"
-  '';
-
-in {
-  options.services.peering-manager = with lib; {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Enable Peering Manager.
-
-        This module requires a reverse proxy that serves `/static` separately.
-        See this [example](https://github.com/peering-manager/contrib/blob/main/nginx.conf on how to configure this.
-      '';
-    };
-
-    enableScheduledTasks = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Set up [scheduled tasks](https://peering-manager.readthedocs.io/en/stable/setup/8-scheduled-tasks/)
-      '';
-    };
-
-    listenAddress = mkOption {
-      type = types.str;
-      default = "[::1]";
-      description = ''
-        Address the server will listen on.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 8001;
-      description = ''
-        Port the server will listen on.
-      '';
-    };
-
-    plugins = mkOption {
-      type = types.functionTo (types.listOf types.package);
-      default = _: [];
-      defaultText = literalExpression ''
-        python3Packages: with python3Packages; [];
-      '';
-      description = ''
-        List of plugin packages to install.
-      '';
-    };
-
-    secretKeyFile = mkOption {
-      type = types.path;
-      description = ''
-        Path to a file containing the secret key.
-      '';
-    };
-
-    peeringdbApiKeyFile = mkOption {
-      type = with types; nullOr path;
-      default = null;
-      description = ''
-        Path to a file containing the PeeringDB API key.
-      '';
-    };
-
-    settings = lib.mkOption {
-      description = ''
-        Configuration options to set in `configuration.py`.
-        See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
-      '';
-
-      default = { };
-
-      type = lib.types.submodule {
-        freeformType = pythonFmt.type;
-
-        options = {
-          ALLOWED_HOSTS = lib.mkOption {
-            type = with lib.types; listOf str;
-            default = ["*"];
-            description = ''
-              A list of valid fully-qualified domain names (FQDNs) and/or IP
-              addresses that can be used to reach the peering manager service.
-            '';
-          };
-        };
-      };
-    };
-
-    extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-      description = ''
-        Additional lines of configuration appended to the `configuration.py`.
-        See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
-      '';
-    };
-
-    enableLdap = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Enable LDAP-Authentication for Peering Manager.
-
-        This requires a configuration file being pass through `ldapConfigPath`.
-      '';
-    };
-
-    ldapConfigPath = mkOption {
-      type = types.path;
-      description = ''
-        Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`.
-        See the [documentation](https://peering-manager.readthedocs.io/en/stable/setup/6-ldap/#configuration) for possible options.
-      '';
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    services.peering-manager = {
-      settings = {
-        DATABASE = {
-          NAME = "peering-manager";
-          USER = "peering-manager";
-          HOST = "/run/postgresql";
-        };
-
-        # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
-        # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
-        # to use two separate database IDs.
-        REDIS = {
-          tasks = {
-            UNIX_SOCKET_PATH = config.services.redis.servers.peering-manager.unixSocket;
-            DATABASE = 0;
-          };
-          caching = {
-            UNIX_SOCKET_PATH = config.services.redis.servers.peering-manager.unixSocket;
-            DATABASE = 1;
-          };
-        };
-      };
-
-      extraConfig = ''
-        with open("${cfg.secretKeyFile}", "r") as file:
-          SECRET_KEY = file.readline()
-      '' + lib.optionalString (cfg.peeringdbApiKeyFile != null) ''
-        with open("${cfg.peeringdbApiKeyFile}", "r") as file:
-          PEERINGDB_API_KEY = file.readline()
-      '';
-
-      plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
-    };
-
-    system.build.peeringManagerPkg = pkg;
-
-    services.redis.servers.peering-manager.enable = true;
-
-    services.postgresql = {
-      enable = true;
-      ensureDatabases = [ "peering-manager" ];
-      ensureUsers = [
-        {
-          name = "peering-manager";
-          ensureDBOwnership = true;
-        }
-      ];
-    };
-
-    environment.systemPackages = [ peeringManagerManageScript ];
-
-    systemd.targets.peering-manager = {
-      description = "Target for all Peering Manager services";
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network-online.target" "redis-peering-manager.service" ];
-    };
-
-    systemd.services = let
-      defaults = {
-        environment = {
-          PYTHONPATH = pkg.pythonPath;
-        };
-        serviceConfig = {
-          WorkingDirectory = "/var/lib/peering-manager";
-          User = "peering-manager";
-          Group = "peering-manager";
-          StateDirectory = "peering-manager";
-          StateDirectoryMode = "0750";
-          Restart = "on-failure";
-        };
-      };
-    in {
-      peering-manager-migration = lib.recursiveUpdate defaults {
-        description = "Peering Manager migrations";
-        wantedBy = [ "peering-manager.target" ];
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = "${pkg}/bin/peering-manager migrate";
-        };
-      };
-
-      peering-manager = lib.recursiveUpdate defaults {
-        description = "Peering Manager WSGI Service";
-        wantedBy = [ "peering-manager.target" ];
-        after = [ "peering-manager-migration.service" ];
-
-        preStart = ''
-          ${pkg}/bin/peering-manager remove_stale_contenttypes --no-input
-        '';
-
-        serviceConfig = {
-          ExecStart = ''
-            ${pkg.python.pkgs.gunicorn}/bin/gunicorn peering_manager.wsgi \
-              --bind ${cfg.listenAddress}:${toString cfg.port} \
-              --pythonpath ${pkg}/opt/peering-manager
-          '';
-        };
-      };
-
-      peering-manager-rq = lib.recursiveUpdate defaults {
-        description = "Peering Manager Request Queue Worker";
-        wantedBy = [ "peering-manager.target" ];
-        after = [ "peering-manager.service" ];
-        serviceConfig.ExecStart = "${pkg}/bin/peering-manager rqworker high default low";
-      };
-
-      peering-manager-housekeeping = lib.recursiveUpdate defaults {
-        description = "Peering Manager housekeeping job";
-        after = [ "peering-manager.service" ];
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = "${pkg}/bin/peering-manager housekeeping";
-        };
-      };
-
-      peering-manager-peeringdb-sync = lib.recursiveUpdate defaults {
-        description = "PeeringDB sync";
-        after = [ "peering-manager.service" ];
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = "${pkg}/bin/peering-manager peeringdb_sync";
-        };
-      };
-
-      peering-manager-prefix-fetch = lib.recursiveUpdate defaults {
-        description = "Fetch IRR AS-SET prefixes";
-        after = [ "peering-manager.service" ];
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = "${pkg}/bin/peering-manager grab_prefixes";
-        };
-      };
-
-      peering-manager-configuration-deployment = lib.recursiveUpdate defaults {
-        description = "Push configuration to routers";
-        after = [ "peering-manager.service" ];
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = "${pkg}/bin/peering-manager configure_routers";
-        };
-      };
-
-      peering-manager-session-poll = lib.recursiveUpdate defaults {
-        description = "Poll peering sessions from routers";
-        after = [ "peering-manager.service" ];
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = "${pkg}/bin/peering-manager poll_bgp_sessions --all";
-        };
-      };
-    };
-
-    systemd.timers = {
-      peering-manager-housekeeping = {
-        description = "Run Peering Manager housekeeping job";
-        wantedBy = [ "timers.target" ];
-        timerConfig.OnCalendar = "daily";
-      };
-
-      peering-manager-peeringdb-sync = {
-        enable = lib.mkDefault cfg.enableScheduledTasks;
-        description = "Sync PeeringDB at 2:30";
-        wantedBy = [ "timers.target" ];
-        timerConfig.OnCalendar = "02:30:00";
-      };
-
-      peering-manager-prefix-fetch = {
-        enable = lib.mkDefault cfg.enableScheduledTasks;
-        description = "Fetch IRR AS-SET prefixes at 4:30";
-        wantedBy = [ "timers.target" ];
-        timerConfig.OnCalendar = "04:30:00";
-      };
-
-      peering-manager-configuration-deployment = {
-        enable = lib.mkDefault cfg.enableScheduledTasks;
-        description = "Push router configuration every hour 5 minutes before full hour";
-        wantedBy = [ "timers.target" ];
-        timerConfig.OnCalendar = "*:55:00";
-      };
-
-      peering-manager-session-poll = {
-        enable = lib.mkDefault cfg.enableScheduledTasks;
-        description = "Poll peering sessions from routers every hour";
-        wantedBy = [ "timers.target" ];
-        timerConfig.OnCalendar = "*:00:00";
-      };
-    };
-
-    users.users.peering-manager = {
-      home = "/var/lib/peering-manager";
-      isSystemUser = true;
-      group = "peering-manager";
-    };
-    users.groups.peering-manager = {};
-    users.groups."${config.services.redis.servers.peering-manager.user}".members = [ "peering-manager" ];
-  };
-
-  meta.maintainers = with lib.maintainers; [ yuka ];
-}
diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
deleted file mode 100644
index e3f15f4f438c..000000000000
--- a/nixos/modules/services/web-apps/peertube.nix
+++ /dev/null
@@ -1,857 +0,0 @@
-{ lib, pkgs, config, options, ... }:
-
-let
-  cfg = config.services.peertube;
-  opt = options.services.peertube;
-
-  settingsFormat = pkgs.formats.json {};
-  configFile = settingsFormat.generate "production.json" cfg.settings;
-
-  env = {
-    NODE_CONFIG_DIR = "/var/lib/peertube/config";
-    NODE_ENV = "production";
-    NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";
-    NPM_CONFIG_CACHE = "/var/cache/peertube/.npm";
-    NPM_CONFIG_PREFIX = cfg.package;
-    HOME = cfg.package;
-  };
-
-  systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@memlock" "@mount" "@obsolete" "@privileged" "@setuid" ];
-
-  cfgService = {
-    # Proc filesystem
-    ProcSubset = "pid";
-    ProtectProc = "invisible";
-    # Access write directories
-    UMask = "0027";
-    # Capabilities
-    CapabilityBoundingSet = "";
-    # Security
-    NoNewPrivileges = true;
-    # Sandboxing
-    ProtectSystem = "strict";
-    ProtectHome = true;
-    PrivateTmp = true;
-    PrivateDevices = true;
-    PrivateUsers = true;
-    ProtectClock = true;
-    ProtectHostname = true;
-    ProtectKernelLogs = true;
-    ProtectKernelModules = true;
-    ProtectKernelTunables = true;
-    ProtectControlGroups = true;
-    RestrictNamespaces = true;
-    LockPersonality = true;
-    RestrictRealtime = true;
-    RestrictSUIDSGID = true;
-    RemoveIPC = true;
-    PrivateMounts = true;
-    # System Call Filtering
-    SystemCallArchitectures = "native";
-  };
-
-  envFile = pkgs.writeText "peertube.env" (lib.concatMapStrings (s: s + "\n") (
-    (lib.concatLists (lib.mapAttrsToList (name: value:
-      lib.optional (value != null) ''${name}="${toString value}"''
-    ) env))));
-
-  peertubeEnv = pkgs.writeShellScriptBin "peertube-env" ''
-    set -a
-    source "${envFile}"
-    eval -- "\$@"
-  '';
-
-  nginxCommonHeaders = lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.forceSSL ''
-    add_header Strict-Transport-Security 'max-age=31536000';
-  '' + lib.optionalString (config.services.nginx.virtualHosts.${cfg.localDomain}.quic && config.services.nginx.virtualHosts.${cfg.localDomain}.http3) ''
-    add_header Alt-Svc 'h3=":$server_port"; ma=604800';
-  '';
-
-  nginxCommonHeadersExtra = ''
-    add_header Access-Control-Allow-Origin '*';
-    add_header Access-Control-Allow-Methods 'GET, OPTIONS';
-    add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
-  '';
-
-in {
-  options.services.peertube = {
-    enable = lib.mkEnableOption "Peertube";
-
-    user = lib.mkOption {
-      type = lib.types.str;
-      default = "peertube";
-      description = "User account under which Peertube runs.";
-    };
-
-    group = lib.mkOption {
-      type = lib.types.str;
-      default = "peertube";
-      description = "Group under which Peertube runs.";
-    };
-
-    localDomain = lib.mkOption {
-      type = lib.types.str;
-      example = "peertube.example.com";
-      description = "The domain serving your PeerTube instance.";
-    };
-
-    listenHttp = lib.mkOption {
-      type = lib.types.port;
-      default = 9000;
-      description = "The port that the local PeerTube web server will listen on.";
-    };
-
-    listenWeb = lib.mkOption {
-      type = lib.types.port;
-      default = 9000;
-      description = "The public-facing port that PeerTube will be accessible at (likely 80 or 443 if running behind a reverse proxy). Clients will try to access PeerTube at this port.";
-    };
-
-    enableWebHttps = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = "Whether clients will access your PeerTube instance with HTTPS. Does NOT configure the PeerTube webserver itself to listen for incoming HTTPS connections.";
-    };
-
-    dataDirs = lib.mkOption {
-      type = lib.types.listOf lib.types.path;
-      default = [ ];
-      example = [ "/opt/peertube/storage" "/var/cache/peertube" ];
-      description = "Allow access to custom data locations.";
-    };
-
-    serviceEnvironmentFile = lib.mkOption {
-      type = lib.types.nullOr lib.types.path;
-      default = null;
-      example = "/run/keys/peertube/password-init-root";
-      description = ''
-        Set environment variables for the service. Mainly useful for setting the initial root password.
-        For example write to file:
-        PT_INITIAL_ROOT_PASSWORD=changeme
-      '';
-    };
-
-    settings = lib.mkOption {
-      type = settingsFormat.type;
-      example = lib.literalExpression ''
-        {
-          listen = {
-            hostname = "0.0.0.0";
-          };
-          log = {
-            level = "debug";
-          };
-          storage = {
-            tmp = "/opt/data/peertube/storage/tmp/";
-            logs = "/opt/data/peertube/storage/logs/";
-            cache = "/opt/data/peertube/storage/cache/";
-          };
-        }
-      '';
-      description = "Configuration for peertube.";
-    };
-
-    configureNginx = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = "Configure nginx as a reverse proxy for peertube.";
-    };
-
-    secrets = {
-      secretsFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/run/secrets/peertube";
-        description = ''
-          Secrets to run PeerTube.
-          Generate one using `openssl rand -hex 32`
-        '';
-      };
-    };
-
-    database = {
-      createLocally = lib.mkOption {
-        type = lib.types.bool;
-        default = false;
-        description = "Configure local PostgreSQL database server for PeerTube.";
-      };
-
-      host = lib.mkOption {
-        type = lib.types.str;
-        default = if cfg.database.createLocally then "/run/postgresql" else null;
-        defaultText = lib.literalExpression ''
-          if config.${opt.database.createLocally}
-          then "/run/postgresql"
-          else null
-        '';
-        example = "192.168.15.47";
-        description = "Database host address or unix socket.";
-      };
-
-      port = lib.mkOption {
-        type = lib.types.port;
-        default = 5432;
-        description = "Database host port.";
-      };
-
-      name = lib.mkOption {
-        type = lib.types.str;
-        default = "peertube";
-        description = "Database name.";
-      };
-
-      user = lib.mkOption {
-        type = lib.types.str;
-        default = "peertube";
-        description = "Database user.";
-      };
-
-      passwordFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/run/keys/peertube/password-postgresql";
-        description = "Password for PostgreSQL database.";
-      };
-    };
-
-    redis = {
-      createLocally = lib.mkOption {
-        type = lib.types.bool;
-        default = false;
-        description = "Configure local Redis server for PeerTube.";
-      };
-
-      host = lib.mkOption {
-        type = lib.types.nullOr lib.types.str;
-        default = if cfg.redis.createLocally && !cfg.redis.enableUnixSocket then "127.0.0.1" else null;
-        defaultText = lib.literalExpression ''
-          if config.${opt.redis.createLocally} && !config.${opt.redis.enableUnixSocket}
-          then "127.0.0.1"
-          else null
-        '';
-        description = "Redis host.";
-      };
-
-      port = lib.mkOption {
-        type = lib.types.nullOr lib.types.port;
-        default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 31638;
-        defaultText = lib.literalExpression ''
-          if config.${opt.redis.createLocally} && config.${opt.redis.enableUnixSocket}
-          then null
-          else 6379
-        '';
-        description = "Redis port.";
-      };
-
-      passwordFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/run/keys/peertube/password-redis-db";
-        description = "Password for redis database.";
-      };
-
-      enableUnixSocket = lib.mkOption {
-        type = lib.types.bool;
-        default = cfg.redis.createLocally;
-        defaultText = lib.literalExpression "config.${opt.redis.createLocally}";
-        description = "Use Unix socket.";
-      };
-    };
-
-    smtp = {
-      createLocally = lib.mkOption {
-        type = lib.types.bool;
-        default = false;
-        description = "Configure local Postfix SMTP server for PeerTube.";
-      };
-
-      passwordFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/run/keys/peertube/password-smtp";
-        description = "Password for smtp server.";
-      };
-    };
-
-    package = lib.mkOption {
-      type = lib.types.package;
-      default = pkgs.peertube;
-      defaultText = lib.literalExpression "pkgs.peertube";
-      description = "PeerTube package to use.";
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    assertions = [
-      { assertion = cfg.serviceEnvironmentFile == null || !lib.hasPrefix builtins.storeDir cfg.serviceEnvironmentFile;
-          message = ''
-            <option>services.peertube.serviceEnvironmentFile</option> points to
-            a file in the Nix store. You should use a quoted absolute path to
-            prevent this.
-          '';
-      }
-      { assertion = cfg.secrets.secretsFile != null;
-          message = ''
-            <option>services.peertube.secrets.secretsFile</option> needs to be set.
-          '';
-      }
-      { assertion = !(cfg.redis.enableUnixSocket && (cfg.redis.host != null || cfg.redis.port != null));
-          message = ''
-            <option>services.peertube.redis.createLocally</option> and redis network connection (<option>services.peertube.redis.host</option> or <option>services.peertube.redis.port</option>) enabled. Disable either of them.
-        '';
-      }
-      { assertion = cfg.redis.enableUnixSocket || (cfg.redis.host != null && cfg.redis.port != null);
-          message = ''
-            <option>services.peertube.redis.host</option> and <option>services.peertube.redis.port</option> needs to be set if <option>services.peertube.redis.enableUnixSocket</option> is not enabled.
-        '';
-      }
-      { assertion = cfg.redis.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.redis.passwordFile;
-          message = ''
-            <option>services.peertube.redis.passwordFile</option> points to
-            a file in the Nix store. You should use a quoted absolute path to
-            prevent this.
-          '';
-      }
-      { assertion = cfg.database.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.database.passwordFile;
-          message = ''
-            <option>services.peertube.database.passwordFile</option> points to
-            a file in the Nix store. You should use a quoted absolute path to
-            prevent this.
-          '';
-      }
-      { assertion = cfg.smtp.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.smtp.passwordFile;
-          message = ''
-            <option>services.peertube.smtp.passwordFile</option> points to
-            a file in the Nix store. You should use a quoted absolute path to
-            prevent this.
-          '';
-      }
-    ];
-
-    environment.systemPackages = [ cfg.package.cli ];
-
-    services.peertube.settings = lib.mkMerge [
-      {
-        listen = {
-          port = cfg.listenHttp;
-        };
-        webserver = {
-          https = (if cfg.enableWebHttps then true else false);
-          hostname = "${cfg.localDomain}";
-          port = cfg.listenWeb;
-        };
-        database = {
-          hostname = "${cfg.database.host}";
-          port = cfg.database.port;
-          name = "${cfg.database.name}";
-          username = "${cfg.database.user}";
-        };
-        redis = {
-          hostname = "${toString cfg.redis.host}";
-          port = (lib.optionalString (cfg.redis.port != null) cfg.redis.port);
-        };
-        storage = {
-          tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/";
-          tmp_persistent = lib.mkDefault "/var/lib/peertube/storage/tmp_persistent/";
-          bin = lib.mkDefault "/var/lib/peertube/storage/bin/";
-          avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/";
-          web_videos = lib.mkDefault "/var/lib/peertube/storage/web-videos/";
-          streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/";
-          redundancy = lib.mkDefault "/var/lib/peertube/storage/redundancy/";
-          logs = lib.mkDefault "/var/lib/peertube/storage/logs/";
-          previews = lib.mkDefault "/var/lib/peertube/storage/previews/";
-          thumbnails = lib.mkDefault "/var/lib/peertube/storage/thumbnails/";
-          storyboards = lib.mkDefault "/var/lib/peertube/storage/storyboards/";
-          torrents = lib.mkDefault "/var/lib/peertube/storage/torrents/";
-          captions = lib.mkDefault "/var/lib/peertube/storage/captions/";
-          cache = lib.mkDefault "/var/lib/peertube/storage/cache/";
-          plugins = lib.mkDefault "/var/lib/peertube/storage/plugins/";
-          well_known = lib.mkDefault "/var/lib/peertube/storage/well_known/";
-          client_overrides = lib.mkDefault "/var/lib/peertube/storage/client-overrides/";
-        };
-        import = {
-          videos = {
-            http = {
-              youtube_dl_release = {
-                python_path = "${pkgs.python3}/bin/python";
-              };
-            };
-          };
-        };
-      }
-      (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis-peertube/redis.sock"; }; })
-    ];
-
-    systemd.tmpfiles.rules = [
-      "d '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
-      "z '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
-      "d '/var/lib/peertube/www' 0750 ${cfg.user} ${cfg.group} - -"
-      "z '/var/lib/peertube/www' 0750 ${cfg.user} ${cfg.group} - -"
-    ];
-
-    systemd.services.peertube-init-db = lib.mkIf cfg.database.createLocally {
-      description = "Initialization database for PeerTube daemon";
-      after = [ "network.target" "postgresql.service" ];
-      requires = [ "postgresql.service" ];
-
-      script = let
-        psqlSetupCommands = pkgs.writeText "peertube-init.sql" ''
-          SELECT 'CREATE USER "${cfg.database.user}"' WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${cfg.database.user}')\gexec
-          SELECT 'CREATE DATABASE "${cfg.database.name}" OWNER "${cfg.database.user}" TEMPLATE template0 ENCODING UTF8' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${cfg.database.name}')\gexec
-          \c '${cfg.database.name}'
-          CREATE EXTENSION IF NOT EXISTS pg_trgm;
-          CREATE EXTENSION IF NOT EXISTS unaccent;
-        '';
-      in "${config.services.postgresql.package}/bin/psql -f ${psqlSetupCommands}";
-
-      serviceConfig = {
-        Type = "oneshot";
-        WorkingDirectory = cfg.package;
-        # User and group
-        User = "postgres";
-        Group = "postgres";
-        # Sandboxing
-        RestrictAddressFamilies = [ "AF_UNIX" ];
-        MemoryDenyWriteExecute = true;
-        # System Call Filtering
-        SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ]);
-      } // cfgService;
-    };
-
-    systemd.services.peertube = {
-      description = "PeerTube daemon";
-      after = [ "network.target" ]
-        ++ lib.optional cfg.redis.createLocally "redis-peertube.service"
-        ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ];
-      requires = lib.optional cfg.redis.createLocally "redis-peertube.service"
-        ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ];
-      wantedBy = [ "multi-user.target" ];
-
-      environment = env;
-
-      path = with pkgs; [ nodejs_18 yarn ffmpeg-headless openssl ];
-
-      script = ''
-        #!/bin/sh
-        umask 077
-        cat > /var/lib/peertube/config/local.yaml <<EOF
-        ${lib.optionalString (cfg.secrets.secretsFile != null) ''
-        secrets:
-          peertube: '$(cat ${cfg.secrets.secretsFile})'
-        ''}
-        ${lib.optionalString ((!cfg.database.createLocally) && (cfg.database.passwordFile != null)) ''
-        database:
-          password: '$(cat ${cfg.database.passwordFile})'
-        ''}
-        ${lib.optionalString (cfg.redis.passwordFile != null) ''
-        redis:
-          auth: '$(cat ${cfg.redis.passwordFile})'
-        ''}
-        ${lib.optionalString (cfg.smtp.passwordFile != null) ''
-        smtp:
-          password: '$(cat ${cfg.smtp.passwordFile})'
-        ''}
-        EOF
-        umask 027
-        ln -sf ${configFile} /var/lib/peertube/config/production.json
-        ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml
-        ln -sf ${cfg.package}/client/dist -T /var/lib/peertube/www/client
-        ln -sf ${cfg.settings.storage.client_overrides} -T /var/lib/peertube/www/client-overrides
-        node dist/server
-      '';
-      serviceConfig = {
-        Type = "simple";
-        Restart = "always";
-        RestartSec = 20;
-        TimeoutSec = 60;
-        WorkingDirectory = cfg.package;
-        SyslogIdentifier = "peertube";
-        # User and group
-        User = cfg.user;
-        Group = cfg.group;
-        # State directory and mode
-        StateDirectory = "peertube";
-        StateDirectoryMode = "0750";
-        # Cache directory and mode
-        CacheDirectory = "peertube";
-        CacheDirectoryMode = "0750";
-        # Access write directories
-        ReadWritePaths = cfg.dataDirs;
-        # Environment
-        EnvironmentFile = cfg.serviceEnvironmentFile;
-        # Sandboxing
-        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
-        MemoryDenyWriteExecute = false;
-        # System Call Filtering
-        SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "pipe" "pipe2" ];
-      } // cfgService;
-    };
-
-    services.nginx = lib.mkIf cfg.configureNginx {
-      enable = true;
-      upstreams."peertube".servers = {
-        "127.0.0.1:${toString cfg.listenHttp}".fail_timeout = "0";
-      };
-      virtualHosts."${cfg.localDomain}" = {
-        root = "/var/lib/peertube/www";
-
-        # Application
-        locations."/" = {
-          tryFiles = "/dev/null @api";
-          priority = 1110;
-        };
-
-        locations."~ ^/api/v1/videos/(upload-resumable|([^/]+/source/replace-resumable))$" = {
-          tryFiles = "/dev/null @api";
-          priority = 1120;
-
-          extraConfig = ''
-            client_max_body_size 0;
-            proxy_request_buffering off;
-          '' + nginxCommonHeaders;
-        };
-
-        locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = {
-          tryFiles = "/dev/null @api";
-          root = cfg.settings.storage.tmp;
-          priority = 1130;
-
-          extraConfig = ''
-            limit_except POST HEAD { deny all; }
-
-            client_max_body_size 12G;
-            add_header X-File-Maximum-Size 8G always;
-          '' + nginxCommonHeaders;
-        };
-
-        locations."~ ^/api/v1/runners/jobs/[^/]+/(update|success)$" = {
-          tryFiles = "/dev/null @api";
-          root = cfg.settings.storage.tmp;
-          priority = 1135;
-
-          extraConfig = ''
-            client_max_body_size 12G;
-            add_header X-File-Maximum-Size 8G always;
-          '' + nginxCommonHeaders;
-        };
-
-        locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = {
-          tryFiles = "/dev/null @api";
-          priority = 1140;
-
-          extraConfig = ''
-            client_max_body_size 6M;
-            add_header X-File-Maximum-Size 4M always;
-          '' + nginxCommonHeaders;
-        };
-
-        locations."@api" = {
-          proxyPass = "http://peertube";
-          priority = 1150;
-
-          extraConfig = ''
-            proxy_set_header Host $host;
-            proxy_set_header X-Real-IP $remote_addr;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-
-            proxy_connect_timeout 10m;
-
-            proxy_send_timeout 10m;
-            proxy_read_timeout 10m;
-
-            client_max_body_size 100k;
-            send_timeout 10m;
-          ''+ nginxCommonHeaders;
-        };
-
-        # Websocket
-        locations."/socket.io" = {
-          tryFiles = "/dev/null @api_websocket";
-          priority = 1210;
-        };
-
-        locations."/tracker/socket" = {
-          tryFiles = "/dev/null @api_websocket";
-          priority = 1220;
-
-          extraConfig = ''
-            proxy_read_timeout 15m;
-          '';
-        };
-
-        locations."~ ^/plugins/[^/]+(/[^/]+)?/ws/" = {
-          tryFiles = "/dev/null @api_websocket";
-          priority = 1230;
-        };
-
-        locations."@api_websocket" = {
-          proxyPass = "http://peertube";
-          priority = 1240;
-
-          extraConfig = ''
-            proxy_http_version 1.1;
-            proxy_set_header Upgrade $http_upgrade;
-            proxy_set_header Connection 'upgrade';
-            proxy_set_header Host $host;
-            proxy_set_header X-Real-IP $remote_addr;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-
-          '' + nginxCommonHeaders;
-        };
-
-        # Bypass PeerTube for performance reasons.
-        locations."~ ^/client/(assets/images/(icons/icon-36x36\.png|icons/icon-48x48\.png|icons/icon-72x72\.png|icons/icon-96x96\.png|icons/icon-144x144\.png|icons/icon-192x192\.png|icons/icon-512x512\.png|logo\.svg|favicon\.png|default-playlist\.jpg|default-avatar-account\.png|default-avatar-account-48x48\.png|default-avatar-video-channel\.png|default-avatar-video-channel-48x48\.png))$" = {
-          tryFiles = "/client-overrides/$1 /client/$1 $1";
-          priority = 1310;
-
-          extraConfig = nginxCommonHeaders;
-        };
-
-        locations."~ ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$" = {
-          alias = "${cfg.package}/client/dist/$1";
-          priority = 1320;
-          extraConfig = ''
-            add_header Cache-Control 'public, max-age=604800, immutable';
-          '' + nginxCommonHeaders;
-        };
-
-        locations."^~ /download/" = {
-          proxyPass = "http://peertube";
-          priority = 1410;
-          extraConfig = ''
-            proxy_set_header Host $host;
-            proxy_set_header X-Real-IP $remote_addr;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-
-            proxy_limit_rate 5M;
-          '' + nginxCommonHeaders;
-        };
-
-        locations."^~ /static/streaming-playlists/hls/private/" = {
-          proxyPass = "http://peertube";
-          priority = 1420;
-          extraConfig = ''
-            proxy_set_header Host $host;
-            proxy_set_header X-Real-IP $remote_addr;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-
-            proxy_limit_rate 5M;
-          '' + nginxCommonHeaders;
-        };
-
-        locations."^~ /static/web-videos/private/" = {
-          proxyPass = "http://peertube";
-          priority = 1430;
-          extraConfig = ''
-            proxy_set_header Host $host;
-            proxy_set_header X-Real-IP $remote_addr;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-
-            proxy_limit_rate 5M;
-          '' + nginxCommonHeaders;
-        };
-
-        locations."^~ /static/webseed/private/" = {
-          proxyPass = "http://peertube";
-          priority = 1440;
-          extraConfig = ''
-            proxy_set_header Host $host;
-            proxy_set_header X-Real-IP $remote_addr;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-
-            proxy_limit_rate 5M;
-          '' + nginxCommonHeaders;
-        };
-
-        locations."^~ /static/redundancy/" = {
-          tryFiles = "$uri @api";
-          root = cfg.settings.storage.redundancy;
-          priority = 1450;
-          extraConfig = ''
-            set $peertube_limit_rate 800k;
-
-            if ($request_uri ~ -fragmented.mp4$) {
-              set $peertube_limit_rate 5M;
-            }
-
-            if ($request_method = 'OPTIONS') {
-              ${nginxCommonHeaders}
-              ${nginxCommonHeadersExtra}
-              add_header Access-Control-Max-Age 1728000;
-              add_header Content-Type 'text/plain charset=UTF-8';
-              add_header Content-Length 0;
-              return 204;
-            }
-            if ($request_method = 'GET') {
-              ${nginxCommonHeaders}
-              ${nginxCommonHeadersExtra}
-
-              access_log off;
-            }
-
-            aio threads;
-            sendfile on;
-            sendfile_max_chunk 1M;
-
-            limit_rate $peertube_limit_rate;
-            limit_rate_after 5M;
-
-            rewrite ^/static/redundancy/(.*)$ /$1 break;
-          '';
-        };
-
-        locations."^~ /static/streaming-playlists/" = {
-          tryFiles = "$uri @api";
-          root = cfg.settings.storage.streaming_playlists;
-          priority = 1460;
-          extraConfig = ''
-            set $peertube_limit_rate 800k;
-
-            if ($request_uri ~ -fragmented.mp4$) {
-              set $peertube_limit_rate 5M;
-            }
-
-            if ($request_method = 'OPTIONS') {
-              ${nginxCommonHeaders}
-              ${nginxCommonHeadersExtra}
-              add_header Access-Control-Max-Age 1728000;
-              add_header Content-Type 'text/plain charset=UTF-8';
-              add_header Content-Length 0;
-              return 204;
-            }
-            if ($request_method = 'GET') {
-              ${nginxCommonHeaders}
-              ${nginxCommonHeadersExtra}
-
-              access_log off;
-            }
-
-            aio threads;
-            sendfile on;
-            sendfile_max_chunk 1M;
-
-            limit_rate $peertube_limit_rate;
-            limit_rate_after 5M;
-
-            rewrite ^/static/streaming-playlists/(.*)$ /$1 break;
-          '';
-        };
-
-        locations."^~ /static/web-videos/" = {
-          tryFiles = "$uri @api";
-          root = cfg.settings.storage.web_videos;
-          priority = 1470;
-          extraConfig = ''
-            set $peertube_limit_rate 800k;
-
-            if ($request_uri ~ -fragmented.mp4$) {
-              set $peertube_limit_rate 5M;
-            }
-
-            if ($request_method = 'OPTIONS') {
-              ${nginxCommonHeaders}
-              ${nginxCommonHeadersExtra}
-              add_header Access-Control-Max-Age 1728000;
-              add_header Content-Type 'text/plain charset=UTF-8';
-              add_header Content-Length 0;
-              return 204;
-            }
-            if ($request_method = 'GET') {
-              ${nginxCommonHeaders}
-              ${nginxCommonHeadersExtra}
-
-              access_log off;
-            }
-
-            aio threads;
-            sendfile on;
-            sendfile_max_chunk 1M;
-
-            limit_rate $peertube_limit_rate;
-            limit_rate_after 5M;
-
-            rewrite ^/static/web-videos/(.*)$ /$1 break;
-          '';
-        };
-
-        locations."^~ /static/webseed/" = {
-          tryFiles = "$uri @api";
-          root = cfg.settings.storage.web_videos;
-          priority = 1480;
-          extraConfig = ''
-            set $peertube_limit_rate 800k;
-
-            if ($request_uri ~ -fragmented.mp4$) {
-              set $peertube_limit_rate 5M;
-            }
-
-            if ($request_method = 'OPTIONS') {
-              ${nginxCommonHeaders}
-              ${nginxCommonHeadersExtra}
-              add_header Access-Control-Max-Age 1728000;
-              add_header Content-Type 'text/plain charset=UTF-8';
-              add_header Content-Length 0;
-              return 204;
-            }
-            if ($request_method = 'GET') {
-              ${nginxCommonHeaders}
-              ${nginxCommonHeadersExtra}
-
-              access_log off;
-            }
-
-            aio threads;
-            sendfile on;
-            sendfile_max_chunk 1M;
-
-            limit_rate $peertube_limit_rate;
-            limit_rate_after 5M;
-
-            rewrite ^/static/webseed/(.*)$ /web-videos/$1 break;
-          '';
-        };
-      };
-    };
-
-    services.postgresql = lib.mkIf cfg.database.createLocally {
-      enable = true;
-    };
-
-    services.redis.servers.peertube = lib.mkMerge [
-      (lib.mkIf cfg.redis.createLocally {
-        enable = true;
-      })
-      (lib.mkIf (cfg.redis.createLocally && !cfg.redis.enableUnixSocket) {
-        bind = "127.0.0.1";
-        port = cfg.redis.port;
-      })
-      (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {
-        unixSocket = "/run/redis-peertube/redis.sock";
-        unixSocketPerm = 660;
-      })
-    ];
-
-    services.postfix = lib.mkIf cfg.smtp.createLocally {
-      enable = true;
-      hostname = lib.mkDefault "${cfg.localDomain}";
-    };
-
-    users.users = lib.mkMerge [
-      (lib.mkIf (cfg.user == "peertube") {
-        peertube = {
-          isSystemUser = true;
-          group = cfg.group;
-          home = cfg.package;
-        };
-      })
-      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ peertubeEnv pkgs.nodejs_18 pkgs.yarn pkgs.ffmpeg-headless ])
-      (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];})
-    ];
-
-    users.groups = {
-      ${cfg.group} = {
-        members = lib.optional cfg.configureNginx config.services.nginx.user;
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix b/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
deleted file mode 100644
index f1e5f022c379..000000000000
--- a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
+++ /dev/null
@@ -1,71 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.pgpkeyserver-lite;
-  sksCfg = config.services.sks;
-  sksOpt = options.services.sks;
-
-  webPkg = cfg.package;
-
-in
-
-{
-
-  options = {
-
-    services.pgpkeyserver-lite = {
-
-      enable = mkEnableOption "pgpkeyserver-lite on a nginx vHost proxying to a gpg keyserver";
-
-      package = mkPackageOption pkgs "pgpkeyserver-lite" { };
-
-      hostname = mkOption {
-        type = types.str;
-        description = ''
-          Which hostname to set the vHost to that is proxying to sks.
-        '';
-      };
-
-      hkpAddress = mkOption {
-        default = builtins.head sksCfg.hkpAddress;
-        defaultText = literalExpression "head config.${sksOpt.hkpAddress}";
-        type = types.str;
-        description = ''
-          Which IP address the sks-keyserver is listening on.
-        '';
-      };
-
-      hkpPort = mkOption {
-        default = sksCfg.hkpPort;
-        defaultText = literalExpression "config.${sksOpt.hkpPort}";
-        type = types.int;
-        description = ''
-          Which port the sks-keyserver is listening on.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    services.nginx.enable = true;
-
-    services.nginx.virtualHosts = let
-      hkpPort = builtins.toString cfg.hkpPort;
-    in {
-      ${cfg.hostname} = {
-        root = webPkg;
-        locations = {
-          "/pks".extraConfig = ''
-            proxy_pass         http://${cfg.hkpAddress}:${hkpPort};
-            proxy_pass_header  Server;
-            add_header         Via "1.1 ${cfg.hostname}";
-          '';
-        };
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/photoprism.nix b/nixos/modules/services/web-apps/photoprism.nix
deleted file mode 100644
index ec4126b420cd..000000000000
--- a/nixos/modules/services/web-apps/photoprism.nix
+++ /dev/null
@@ -1,156 +0,0 @@
-{ config, pkgs, lib, ... }:
-let
-  cfg = config.services.photoprism;
-
-  env = {
-    PHOTOPRISM_ORIGINALS_PATH = cfg.originalsPath;
-    PHOTOPRISM_STORAGE_PATH = cfg.storagePath;
-    PHOTOPRISM_IMPORT_PATH = cfg.importPath;
-    PHOTOPRISM_HTTP_HOST = cfg.address;
-    PHOTOPRISM_HTTP_PORT = toString cfg.port;
-  } // (
-    lib.mapAttrs (_: toString) cfg.settings
-  );
-
-  manage = pkgs.writeShellScript "manage" ''
-    set -o allexport # Export the following env vars
-    ${lib.toShellVars env}
-    eval "$(${config.systemd.package}/bin/systemctl show -pUID,MainPID photoprism.service | ${pkgs.gnused}/bin/sed "s/UID/ServiceUID/")"
-    exec ${pkgs.util-linux}/bin/nsenter \
-      -t $MainPID -m -S $ServiceUID -G $ServiceUID --wdns=${cfg.storagePath} \
-      ${cfg.package}/bin/photoprism "$@"
-  '';
-in
-{
-  meta.maintainers = with lib.maintainers; [ stunkymonkey ];
-
-  options.services.photoprism = {
-
-    enable = lib.mkEnableOption "Photoprism web server";
-
-    passwordFile = lib.mkOption {
-      type = lib.types.nullOr lib.types.path;
-      default = null;
-      description = ''
-        Admin password file.
-      '';
-    };
-
-    address = lib.mkOption {
-      type = lib.types.str;
-      default = "localhost";
-      description = ''
-        Web interface address.
-      '';
-    };
-
-    port = lib.mkOption {
-      type = lib.types.port;
-      default = 2342;
-      description = ''
-        Web interface port.
-      '';
-    };
-
-    originalsPath = lib.mkOption {
-      type = lib.types.path;
-      default = null;
-      example = "/data/photos";
-      description = ''
-        Storage path of your original media files (photos and videos).
-      '';
-    };
-
-    importPath = lib.mkOption {
-      type = lib.types.str;
-      default = "import";
-      description = ''
-        Relative or absolute to the `originalsPath` from where the files should be imported.
-      '';
-    };
-
-    storagePath = lib.mkOption {
-      type = lib.types.path;
-      default = "/var/lib/photoprism";
-      description = ''
-        Location for sidecar, cache, and database files.
-      '';
-    };
-
-    package = lib.mkPackageOption pkgs "photoprism" { };
-
-    settings = lib.mkOption {
-      type = lib.types.attrsOf lib.types.str;
-      default = { };
-      description = ''
-        See [the getting-started guide](https://docs.photoprism.app/getting-started/config-options/) for available options.
-      '';
-      example = {
-        PHOTOPRISM_DEFAULT_LOCALE = "de";
-        PHOTOPRISM_ADMIN_USER = "root";
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.photoprism = {
-      description = "Photoprism server";
-
-      serviceConfig = {
-        Restart = "on-failure";
-        User = "photoprism";
-        Group = "photoprism";
-        DynamicUser = true;
-        StateDirectory = "photoprism";
-        WorkingDirectory = "/var/lib/photoprism";
-        RuntimeDirectory = "photoprism";
-        ReadWritePaths = [ cfg.originalsPath cfg.importPath cfg.storagePath ];
-
-        LoadCredential = lib.optionalString (cfg.passwordFile != null)
-          "PHOTOPRISM_ADMIN_PASSWORD:${cfg.passwordFile}";
-
-        CapabilityBoundingSet = "";
-        LockPersonality = true;
-        PrivateDevices = true;
-        PrivateUsers = true;
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@setuid @keyring" ];
-        UMask = "0066";
-      } // lib.optionalAttrs (cfg.port < 1024) {
-        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
-        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
-      };
-
-      wantedBy = [ "multi-user.target" ];
-      environment = env;
-
-      # reminder: easier password configuration will come in https://github.com/photoprism/photoprism/pull/2302
-      preStart = ''
-        ln -sf ${manage} photoprism-manage
-
-        ${lib.optionalString (cfg.passwordFile != null) ''
-          export PHOTOPRISM_ADMIN_PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/PHOTOPRISM_ADMIN_PASSWORD")
-        ''}
-        exec ${cfg.package}/bin/photoprism migrations run -f
-      '';
-
-      script = ''
-        ${lib.optionalString (cfg.passwordFile != null) ''
-          export PHOTOPRISM_ADMIN_PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/PHOTOPRISM_ADMIN_PASSWORD")
-        ''}
-        exec ${cfg.package}/bin/photoprism start
-      '';
-    };
-  };
-}
-
diff --git a/nixos/modules/services/web-apps/phylactery.nix b/nixos/modules/services/web-apps/phylactery.nix
deleted file mode 100644
index 02a3a1765d90..000000000000
--- a/nixos/modules/services/web-apps/phylactery.nix
+++ /dev/null
@@ -1,46 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-let cfg = config.services.phylactery;
-in {
-  options.services.phylactery = {
-    enable = mkEnableOption "Phylactery server";
-
-    host = mkOption {
-      type = types.str;
-      default = "localhost";
-      description = "Listen host for Phylactery";
-    };
-
-    port = mkOption {
-      type = types.port;
-      description = "Listen port for Phylactery";
-    };
-
-    library = mkOption {
-      type = types.path;
-      description = "Path to CBZ library";
-    };
-
-    package = mkPackageOption pkgs "phylactery" { };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.phylactery = {
-      environment = {
-        PHYLACTERY_ADDRESS = "${cfg.host}:${toString cfg.port}";
-        PHYLACTERY_LIBRARY = "${cfg.library}";
-      };
-
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        ConditionPathExists = cfg.library;
-        DynamicUser = true;
-        ExecStart = "${cfg.package}/bin/phylactery";
-      };
-    };
-  };
-
-  meta.maintainers = with maintainers; [ McSinyx ];
-}
diff --git a/nixos/modules/services/web-apps/pict-rs.md b/nixos/modules/services/web-apps/pict-rs.md
deleted file mode 100644
index 56c51e0d7259..000000000000
--- a/nixos/modules/services/web-apps/pict-rs.md
+++ /dev/null
@@ -1,91 +0,0 @@
-# Pict-rs {#module-services-pict-rs}
-
-pict-rs is a  a simple image hosting service.
-
-## Quickstart {#module-services-pict-rs-quickstart}
-
-the minimum to start pict-rs is
-
-```nix
-{
-  services.pict-rs.enable = true;
-}
-```
-
-this will start the http server on port 8080 by default.
-
-## Usage {#module-services-pict-rs-usage}
-
-pict-rs offers the following endpoints:
-
-- `POST /image` for uploading an image. Uploaded content must be valid multipart/form-data with an
-    image array located within the `images[]` key
-
-    This endpoint returns the following JSON structure on success with a 201 Created status
-    ```json
-    {
-        "files": [
-            {
-                "delete_token": "JFvFhqJA98",
-                "file": "lkWZDRvugm.jpg"
-            },
-            {
-                "delete_token": "kAYy9nk2WK",
-                "file": "8qFS0QooAn.jpg"
-            },
-            {
-                "delete_token": "OxRpM3sf0Y",
-                "file": "1hJaYfGE01.jpg"
-            }
-        ],
-        "msg": "ok"
-    }
-    ```
-- `GET /image/download?url=...` Download an image from a remote server, returning the same JSON
-    payload as the `POST` endpoint
-- `GET /image/original/{file}` for getting a full-resolution image. `file` here is the `file` key from the
-    `/image` endpoint's JSON
-- `GET /image/details/original/{file}` for getting the details of a full-resolution image.
-    The returned JSON is structured like so:
-    ```json
-    {
-        "width": 800,
-        "height": 537,
-        "content_type": "image/webp",
-        "created_at": [
-            2020,
-            345,
-            67376,
-            394363487
-        ]
-    }
-    ```
-- `GET /image/process.{ext}?src={file}&...` get a file with transformations applied.
-    existing transformations include
-    - `identity=true`: apply no changes
-    - `blur={float}`: apply a gaussian blur to the file
-    - `thumbnail={int}`: produce a thumbnail of the image fitting inside an `{int}` by `{int}`
-        square using raw pixel sampling
-    - `resize={int}`: produce a thumbnail of the image fitting inside an `{int}` by `{int}` square
-        using a Lanczos2 filter. This is slower than sampling but looks a bit better in some cases
-    - `crop={int-w}x{int-h}`: produce a cropped version of the image with an `{int-w}` by `{int-h}`
-        aspect ratio. The resulting crop will be centered on the image. Either the width or height
-        of the image will remain full-size, depending on the image's aspect ratio and the requested
-        aspect ratio. For example, a 1600x900 image cropped with a 1x1 aspect ratio will become 900x900. A
-        1600x1100 image cropped with a 16x9 aspect ratio will become 1600x900.
-
-    Supported `ext` file extensions include `png`, `jpg`, and `webp`
-
-    An example of usage could be
-    ```
-    GET /image/process.jpg?src=asdf.png&thumbnail=256&blur=3.0
-    ```
-    which would create a 256x256px JPEG thumbnail and blur it
-- `GET /image/details/process.{ext}?src={file}&...` for getting the details of a processed image.
-    The returned JSON is the same format as listed for the full-resolution details endpoint.
-- `DELETE /image/delete/{delete_token}/{file}` or `GET /image/delete/{delete_token}/{file}` to
-    delete a file, where `delete_token` and `file` are from the `/image` endpoint's JSON
-
-## Missing {#module-services-pict-rs-missing}
-
-- Configuring the secure-api-key is not included yet. The envisioned basic use case is consumption on localhost by other services without exposing the service to the internet.
diff --git a/nixos/modules/services/web-apps/pict-rs.nix b/nixos/modules/services/web-apps/pict-rs.nix
deleted file mode 100644
index 07f84578a59b..000000000000
--- a/nixos/modules/services/web-apps/pict-rs.nix
+++ /dev/null
@@ -1,100 +0,0 @@
-{ lib, pkgs, config, ... }:
-
-let
-  cfg = config.services.pict-rs;
-  inherit (lib) maintainers mkOption types;
-
-  is03 = lib.versionOlder cfg.package.version "0.4.0";
-
-in
-{
-  meta.maintainers = with maintainers; [ happysalada ];
-  meta.doc = ./pict-rs.md;
-
-  options.services.pict-rs = {
-    enable = lib.mkEnableOption "pict-rs server";
-
-    package = lib.mkPackageOption pkgs "pict-rs" { };
-
-    dataDir = mkOption {
-      type = types.path;
-      default = "/var/lib/pict-rs";
-      description = ''
-        The directory where to store the uploaded images & database.
-      '';
-    };
-
-    repoPath = mkOption {
-      type = types.nullOr (types.path);
-      default = null;
-      description = ''
-        The directory where to store the database.
-        This option takes precedence over dataDir.
-      '';
-    };
-
-    storePath = mkOption {
-      type = types.nullOr (types.path);
-      default = null;
-      description = ''
-        The directory where to store the uploaded images.
-        This option takes precedence over dataDir.
-      '';
-    };
-
-    address = mkOption {
-      type = types.str;
-      default = "127.0.0.1";
-      description = ''
-        The IPv4 address to deploy the service to.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 8080;
-      description = ''
-        The port which to bind the service to.
-      '';
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    services.pict-rs.package = lib.mkDefault (
-      # An incompatible db change happened in the transition from 0.3 to 0.4.
-      if lib.versionAtLeast config.system.stateVersion "23.11"
-      then pkgs.pict-rs
-      else pkgs.pict-rs_0_3
-    );
-
-    # Account for config differences between 0.3 and 0.4
-    assertions = [
-      {
-        assertion = !is03 || (cfg.repoPath == null && cfg.storePath == null);
-        message = ''
-          Using `services.pict-rs.repoPath` or `services.pict-rs.storePath` with pict-rs 0.3 or older has no effect.
-        '';
-      }
-    ];
-
-    systemd.services.pict-rs = {
-      # Pict-rs split it's database and image storage paths in 0.4.0.
-      environment =
-        if is03 then {
-          PICTRS__PATH = cfg.dataDir;
-          PICTRS__ADDR = "${cfg.address}:${toString cfg.port}";
-        } else {
-          PICTRS__REPO__PATH = if cfg.repoPath != null then cfg.repoPath else "${cfg.dataDir}/sled-repo";
-          PICTRS__STORE__PATH = if cfg.storePath != null then cfg.storePath else "${cfg.dataDir}/files";
-          PICTRS__SERVER__ADDR = "${cfg.address}:${toString cfg.port}";
-        };
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        DynamicUser = true;
-        StateDirectory = "pict-rs";
-        ExecStart = if is03 then "${lib.getBin cfg.package}/bin/pict-rs" else "${lib.getBin cfg.package}/bin/pict-rs run";
-      };
-    };
-  };
-
-}
diff --git a/nixos/modules/services/web-apps/pixelfed.nix b/nixos/modules/services/web-apps/pixelfed.nix
deleted file mode 100644
index cd0e8f62b65c..000000000000
--- a/nixos/modules/services/web-apps/pixelfed.nix
+++ /dev/null
@@ -1,479 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.pixelfed;
-  user = cfg.user;
-  group = cfg.group;
-  pixelfed = cfg.package.override { inherit (cfg) dataDir runtimeDir; };
-  # https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L185-L190
-  extraPrograms = with pkgs; [ jpegoptim optipng pngquant gifsicle ffmpeg ];
-  # Ensure PHP extensions: https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L135-L147
-  phpPackage = cfg.phpPackage.buildEnv {
-    extensions = { enabled, all }:
-      enabled
-      ++ (with all; [ bcmath ctype curl mbstring gd intl zip redis imagick ]);
-  };
-  configFile =
-    pkgs.writeText "pixelfed-env" (lib.generators.toKeyValue { } cfg.settings);
-  # Management script
-  pixelfed-manage = pkgs.writeShellScriptBin "pixelfed-manage" ''
-    cd ${pixelfed}
-    sudo=exec
-    if [[ "$USER" != ${user} ]]; then
-      sudo='exec /run/wrappers/bin/sudo -u ${user}'
-    fi
-    $sudo ${phpPackage}/bin/php artisan "$@"
-  '';
-  dbSocket = {
-    "pgsql" = "/run/postgresql";
-    "mysql" = "/run/mysqld/mysqld.sock";
-  }.${cfg.database.type};
-  dbService = {
-    "pgsql" = "postgresql.service";
-    "mysql" = "mysql.service";
-  }.${cfg.database.type};
-  redisService = "redis-pixelfed.service";
-in {
-  options.services = {
-    pixelfed = {
-      enable = mkEnableOption "a Pixelfed instance";
-      package = mkPackageOption pkgs "pixelfed" { };
-      phpPackage = mkPackageOption pkgs "php81" { };
-
-      user = mkOption {
-        type = types.str;
-        default = "pixelfed";
-        description = ''
-          User account under which pixelfed runs.
-
-          ::: {.note}
-          If left as the default value this user will automatically be created
-          on system activation, otherwise you are responsible for
-          ensuring the user exists before the pixelfed application starts.
-          :::
-        '';
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "pixelfed";
-        description = ''
-          Group account under which pixelfed runs.
-
-          ::: {.note}
-          If left as the default value this group will automatically be created
-          on system activation, otherwise you are responsible for
-          ensuring the group exists before the pixelfed application starts.
-          :::
-        '';
-      };
-
-      domain = mkOption {
-        type = types.str;
-        description = ''
-          FQDN for the Pixelfed instance.
-        '';
-      };
-
-      secretFile = mkOption {
-        type = types.path;
-        description = ''
-          A secret file to be sourced for the .env settings.
-          Place `APP_KEY` and other settings that should not end up in the Nix store here.
-        '';
-      };
-
-      settings = mkOption {
-        type = with types; (attrsOf (oneOf [ bool int str ]));
-        description = ''
-          .env settings for Pixelfed.
-          Secrets should use `secretFile` option instead.
-        '';
-      };
-
-      nginx = mkOption {
-        type = types.nullOr (types.submodule
-          (import ../web-servers/nginx/vhost-options.nix {
-            inherit config lib;
-          }));
-        default = null;
-        example = lib.literalExpression ''
-          {
-            serverAliases = [
-              "pics.''${config.networking.domain}"
-            ];
-            enableACME = true;
-            forceHttps = true;
-          }
-        '';
-        description = ''
-          With this option, you can customize an nginx virtual host which already has sensible defaults for Dolibarr.
-          Set to {} if you do not need any customization to the virtual host.
-          If enabled, then by default, the {option}`serverName` is
-          `''${domain}`,
-          If this is set to null (the default), no nginx virtualHost will be configured.
-        '';
-      };
-
-      redis.createLocally = mkEnableOption "a local Redis database using UNIX socket authentication"
-        // {
-          default = true;
-        };
-
-      database = {
-        createLocally = mkEnableOption "a local database using UNIX socket authentication" // {
-            default = true;
-          };
-        automaticMigrations = mkEnableOption "automatic migrations for database schema and data" // {
-            default = true;
-          };
-
-        type = mkOption {
-          type = types.enum [ "mysql" "pgsql" ];
-          example = "pgsql";
-          default = "mysql";
-          description = ''
-            Database engine to use.
-            Note that PGSQL is not well supported: https://github.com/pixelfed/pixelfed/issues/2727
-          '';
-        };
-
-        name = mkOption {
-          type = types.str;
-          default = "pixelfed";
-          description = "Database name.";
-        };
-      };
-
-      maxUploadSize = mkOption {
-        type = types.str;
-        default = "8M";
-        description = ''
-          Max upload size with units.
-        '';
-      };
-
-      poolConfig = mkOption {
-        type = with types; attrsOf (oneOf [ int str bool ]);
-        default = { };
-
-        description = ''
-          Options for Pixelfed's PHP-FPM pool.
-        '';
-      };
-
-      dataDir = mkOption {
-        type = types.str;
-        default = "/var/lib/pixelfed";
-        description = ''
-          State directory of the `pixelfed` user which holds
-          the application's state and data.
-        '';
-      };
-
-      runtimeDir = mkOption {
-        type = types.str;
-        default = "/run/pixelfed";
-        description = ''
-          Ruutime directory of the `pixelfed` user which holds
-          the application's caches and temporary files.
-        '';
-      };
-
-      schedulerInterval = mkOption {
-        type = types.str;
-        default = "1d";
-        description = "How often the Pixelfed cron task should run";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    users.users.pixelfed = mkIf (cfg.user == "pixelfed") {
-      isSystemUser = true;
-      group = cfg.group;
-      extraGroups = lib.optional cfg.redis.createLocally "redis-pixelfed";
-    };
-    users.groups.pixelfed = mkIf (cfg.group == "pixelfed") { };
-
-    services.redis.servers.pixelfed.enable = lib.mkIf cfg.redis.createLocally true;
-    services.pixelfed.settings = mkMerge [
-      ({
-        APP_ENV = mkDefault "production";
-        APP_DEBUG = mkDefault false;
-        # https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L312-L316
-        APP_URL = mkDefault "https://${cfg.domain}";
-        ADMIN_DOMAIN = mkDefault cfg.domain;
-        APP_DOMAIN = mkDefault cfg.domain;
-        SESSION_DOMAIN = mkDefault cfg.domain;
-        SESSION_SECURE_COOKIE = mkDefault true;
-        OPEN_REGISTRATION = mkDefault false;
-        # ActivityPub: https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L360-L364
-        ACTIVITY_PUB = mkDefault true;
-        AP_REMOTE_FOLLOW = mkDefault true;
-        AP_INBOX = mkDefault true;
-        AP_OUTBOX = mkDefault true;
-        AP_SHAREDINBOX = mkDefault true;
-        # Image optimization: https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L367-L404
-        PF_OPTIMIZE_IMAGES = mkDefault true;
-        IMAGE_DRIVER = mkDefault "imagick";
-        # Mobile APIs
-        OAUTH_ENABLED = mkDefault true;
-        # https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L351
-        EXP_EMC = mkDefault true;
-        # Defer to systemd
-        LOG_CHANNEL = mkDefault "stderr";
-        # TODO: find out the correct syntax?
-        # TRUST_PROXIES = mkDefault "127.0.0.1/8, ::1/128";
-      })
-      (mkIf (cfg.redis.createLocally) {
-        BROADCAST_DRIVER = mkDefault "redis";
-        CACHE_DRIVER = mkDefault "redis";
-        QUEUE_DRIVER = mkDefault "redis";
-        SESSION_DRIVER = mkDefault "redis";
-        WEBSOCKET_REPLICATION_MODE = mkDefault "redis";
-        # Support phpredis and predis configuration-style.
-        REDIS_SCHEME = "unix";
-        REDIS_HOST = config.services.redis.servers.pixelfed.unixSocket;
-        REDIS_PATH = config.services.redis.servers.pixelfed.unixSocket;
-      })
-      (mkIf (cfg.database.createLocally) {
-        DB_CONNECTION = cfg.database.type;
-        DB_SOCKET = dbSocket;
-        DB_DATABASE = cfg.database.name;
-        DB_USERNAME = user;
-        # No TCP/IP connection.
-        DB_PORT = 0;
-      })
-    ];
-
-    environment.systemPackages = [ pixelfed-manage ];
-
-    services.mysql =
-      mkIf (cfg.database.createLocally && cfg.database.type == "mysql") {
-        enable = mkDefault true;
-        package = mkDefault pkgs.mariadb;
-        ensureDatabases = [ cfg.database.name ];
-        ensureUsers = [{
-          name = user;
-          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
-        }];
-      };
-
-    services.postgresql =
-      mkIf (cfg.database.createLocally && cfg.database.type == "pgsql") {
-        enable = mkDefault true;
-        ensureDatabases = [ cfg.database.name ];
-        ensureUsers = [{
-          name = user;
-        }];
-      };
-
-    # Make each individual option overridable with lib.mkDefault.
-    services.pixelfed.poolConfig = lib.mapAttrs' (n: v: lib.nameValuePair n (lib.mkDefault v)) {
-      "pm" = "dynamic";
-      "php_admin_value[error_log]" = "stderr";
-      "php_admin_flag[log_errors]" = true;
-      "catch_workers_output" = true;
-      "pm.max_children" = "32";
-      "pm.start_servers" = "2";
-      "pm.min_spare_servers" = "2";
-      "pm.max_spare_servers" = "4";
-      "pm.max_requests" = "500";
-    };
-
-    services.phpfpm.pools.pixelfed = {
-      inherit user group;
-      inherit phpPackage;
-
-      phpOptions = ''
-        post_max_size = ${toString cfg.maxUploadSize}
-        upload_max_filesize = ${toString cfg.maxUploadSize}
-        max_execution_time = 600;
-      '';
-
-      settings = {
-        "listen.owner" = user;
-        "listen.group" = group;
-        "listen.mode" = "0660";
-        "catch_workers_output" = "yes";
-      } // cfg.poolConfig;
-    };
-
-    systemd.services.phpfpm-pixelfed.after = [ "pixelfed-data-setup.service" ];
-    systemd.services.phpfpm-pixelfed.requires =
-      [ "pixelfed-horizon.service" "pixelfed-data-setup.service" ]
-      ++ lib.optional cfg.database.createLocally dbService
-      ++ lib.optional cfg.redis.createLocally redisService;
-    # Ensure image optimizations programs are available.
-    systemd.services.phpfpm-pixelfed.path = extraPrograms;
-
-    systemd.services.pixelfed-horizon = {
-      description = "Pixelfed task queueing via Laravel Horizon framework";
-      after = [ "network.target" "pixelfed-data-setup.service" ];
-      requires = [ "pixelfed-data-setup.service" ]
-        ++ (lib.optional cfg.database.createLocally dbService)
-        ++ (lib.optional cfg.redis.createLocally redisService);
-      wantedBy = [ "multi-user.target" ];
-      # Ensure image optimizations programs are available.
-      path = extraPrograms;
-
-      serviceConfig = {
-        Type = "simple";
-        ExecStart = "${pixelfed-manage}/bin/pixelfed-manage horizon";
-        StateDirectory =
-          lib.mkIf (cfg.dataDir == "/var/lib/pixelfed") "pixelfed";
-        User = user;
-        Group = group;
-        Restart = "on-failure";
-      };
-    };
-
-    systemd.timers.pixelfed-cron = {
-      description = "Pixelfed periodic tasks timer";
-      after = [ "pixelfed-data-setup.service" ];
-      requires = [ "phpfpm-pixelfed.service" ];
-      wantedBy = [ "timers.target" ];
-
-      timerConfig = {
-        OnBootSec = cfg.schedulerInterval;
-        OnUnitActiveSec = cfg.schedulerInterval;
-      };
-    };
-
-    systemd.services.pixelfed-cron = {
-      description = "Pixelfed periodic tasks";
-      # Ensure image optimizations programs are available.
-      path = extraPrograms;
-
-      serviceConfig = {
-        ExecStart = "${pixelfed-manage}/bin/pixelfed-manage schedule:run";
-        User = user;
-        Group = group;
-        StateDirectory =
-          lib.mkIf (cfg.dataDir == "/var/lib/pixelfed") "pixelfed";
-      };
-    };
-
-    systemd.services.pixelfed-data-setup = {
-      description =
-        "Pixelfed setup: migrations, environment file update, cache reload, data changes";
-      wantedBy = [ "multi-user.target" ];
-      after = lib.optional cfg.database.createLocally dbService;
-      requires = lib.optional cfg.database.createLocally dbService;
-      path = with pkgs; [ bash pixelfed-manage rsync ] ++ extraPrograms;
-
-      serviceConfig = {
-        Type = "oneshot";
-        User = user;
-        Group = group;
-        StateDirectory =
-          lib.mkIf (cfg.dataDir == "/var/lib/pixelfed") "pixelfed";
-        LoadCredential = "env-secrets:${cfg.secretFile}";
-        UMask = "077";
-      };
-
-      script = ''
-        # Before running any PHP program, cleanup the code cache.
-        # It's necessary if you upgrade the application otherwise you might
-        # try to import non-existent modules.
-        rm -f ${cfg.runtimeDir}/app.php
-        rm -rf ${cfg.runtimeDir}/cache/*
-
-        # Concatenate non-secret .env and secret .env
-        rm -f ${cfg.dataDir}/.env
-        cp --no-preserve=all ${configFile} ${cfg.dataDir}/.env
-        echo -e '\n' >> ${cfg.dataDir}/.env
-        cat "$CREDENTIALS_DIRECTORY/env-secrets" >> ${cfg.dataDir}/.env
-
-        # Link the static storage (package provided) to the runtime storage
-        # Necessary for cities.json and static images.
-        mkdir -p ${cfg.dataDir}/storage
-        rsync -av --no-perms ${pixelfed}/storage-static/ ${cfg.dataDir}/storage
-        chmod -R +w ${cfg.dataDir}/storage
-
-        chmod g+x ${cfg.dataDir}/storage ${cfg.dataDir}/storage/app
-        chmod -R g+rX ${cfg.dataDir}/storage/app/public
-
-        # Link the app.php in the runtime folder.
-        # We cannot link the cache folder only because bootstrap folder needs to be writeable.
-        ln -sf ${pixelfed}/bootstrap-static/app.php ${cfg.runtimeDir}/app.php
-
-        # https://laravel.com/docs/10.x/filesystem#the-public-disk
-        # Creating the public/storage → storage/app/public link
-        # is unnecessary as it's part of the installPhase of pixelfed.
-
-        # Install Horizon
-        # FIXME: require write access to public/ — should be done as part of install — pixelfed-manage horizon:publish
-
-        # Perform the first migration.
-        [[ ! -f ${cfg.dataDir}/.initial-migration ]] && pixelfed-manage migrate --force && touch ${cfg.dataDir}/.initial-migration
-
-        ${lib.optionalString cfg.database.automaticMigrations ''
-          # Force migrate the database.
-          pixelfed-manage migrate --force
-        ''}
-
-        # Import location data
-        pixelfed-manage import:cities
-
-        ${lib.optionalString cfg.settings.ACTIVITY_PUB ''
-          # ActivityPub federation bookkeeping
-          [[ ! -f ${cfg.dataDir}/.instance-actor-created ]] && pixelfed-manage instance:actor && touch ${cfg.dataDir}/.instance-actor-created
-        ''}
-
-        ${lib.optionalString cfg.settings.OAUTH_ENABLED ''
-          # Generate Passport encryption keys
-          [[ ! -f ${cfg.dataDir}/.passport-keys-generated ]] && pixelfed-manage passport:keys && touch ${cfg.dataDir}/.passport-keys-generated
-        ''}
-
-        pixelfed-manage route:cache
-        pixelfed-manage view:cache
-        pixelfed-manage config:cache
-      '';
-    };
-
-    systemd.tmpfiles.rules = [
-      # Cache must live across multiple systemd units runtimes.
-      "d ${cfg.runtimeDir}/                         0700 ${user} ${group} - -"
-      "d ${cfg.runtimeDir}/cache                    0700 ${user} ${group} - -"
-    ];
-
-    # Enable NGINX to access our phpfpm-socket.
-    users.users."${config.services.nginx.user}".extraGroups = [ cfg.group ];
-    services.nginx = mkIf (cfg.nginx != null) {
-      enable = true;
-      virtualHosts."${cfg.domain}" = mkMerge [
-        cfg.nginx
-        {
-          root = lib.mkForce "${pixelfed}/public/";
-          locations."/".tryFiles = "$uri $uri/ /index.php?$query_string";
-          locations."/favicon.ico".extraConfig = ''
-            access_log off; log_not_found off;
-          '';
-          locations."/robots.txt".extraConfig = ''
-            access_log off; log_not_found off;
-          '';
-          locations."~ \\.php$".extraConfig = ''
-            fastcgi_split_path_info ^(.+\.php)(/.+)$;
-            fastcgi_pass unix:${config.services.phpfpm.pools.pixelfed.socket};
-            fastcgi_index index.php;
-          '';
-          locations."~ /\\.(?!well-known).*".extraConfig = ''
-            deny all;
-          '';
-          extraConfig = ''
-            add_header X-Frame-Options "SAMEORIGIN";
-            add_header X-XSS-Protection "1; mode=block";
-            add_header X-Content-Type-Options "nosniff";
-            index index.html index.htm index.php;
-            error_page 404 /index.php;
-            client_max_body_size ${toString cfg.maxUploadSize};
-          '';
-        }
-      ];
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/plantuml-server.nix b/nixos/modules/services/web-apps/plantuml-server.nix
deleted file mode 100644
index 91a5be124d08..000000000000
--- a/nixos/modules/services/web-apps/plantuml-server.nix
+++ /dev/null
@@ -1,153 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  inherit (lib)
-    literalExpression
-    mkEnableOption
-    mkIf
-    mkOption
-    mkPackageOption
-    mkRemovedOptionModule
-    types
-    ;
-
-  cfg = config.services.plantuml-server;
-
-in
-
-{
-  imports = [
-    (mkRemovedOptionModule [ "services" "plantuml-server" "allowPlantumlInclude" ] "This option has been removed from PlantUML.")
-  ];
-
-  options = {
-    services.plantuml-server = {
-      enable = mkEnableOption "PlantUML server";
-
-      package = mkPackageOption pkgs "plantuml-server" { };
-
-      packages = {
-        jdk = mkPackageOption pkgs "jdk" { };
-        jetty = mkPackageOption pkgs "jetty" {
-          default = [ "jetty_11" ];
-          extraDescription = ''
-            At the time of writing (v1.2023.12), PlantUML Server does not support
-            Jetty versions higher than 12.x.
-
-            Jetty 12.x has introduced major breaking changes, see
-            <https://github.com/jetty/jetty.project/releases/tag/jetty-12.0.0> and
-            <https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-migration-11-to-12>
-          '';
-        };
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "plantuml";
-        description = "User which runs PlantUML server.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "plantuml";
-        description = "Group which runs PlantUML server.";
-      };
-
-      home = mkOption {
-        type = types.path;
-        default = "/var/lib/plantuml";
-        description = "Home directory of the PlantUML server instance.";
-      };
-
-      listenHost = mkOption {
-        type = types.str;
-        default = "127.0.0.1";
-        description = "Host to listen on.";
-      };
-
-      listenPort = mkOption {
-        type = types.int;
-        default = 8080;
-        description = "Port to listen on.";
-      };
-
-      plantumlLimitSize = mkOption {
-        type = types.int;
-        default = 4096;
-        description = "Limits image width and height.";
-      };
-
-      graphvizPackage = mkPackageOption pkgs "graphviz" { };
-
-      plantumlStats = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Set it to on to enable statistics report (https://plantuml.com/statistics-report).";
-      };
-
-      httpAuthorization = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = "When calling the proxy endpoint, the value of HTTP_AUTHORIZATION will be used to set the HTTP Authorization header.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.plantuml-server = {
-      description = "PlantUML server";
-      wantedBy = [ "multi-user.target" ];
-      path = [ cfg.home ];
-
-      environment = {
-        PLANTUML_LIMIT_SIZE = builtins.toString cfg.plantumlLimitSize;
-        GRAPHVIZ_DOT = "${cfg.graphvizPackage}/bin/dot";
-        PLANTUML_STATS = if cfg.plantumlStats then "on" else "off";
-        HTTP_AUTHORIZATION = cfg.httpAuthorization;
-      };
-      script = ''
-      ${cfg.packages.jdk}/bin/java \
-        -jar ${cfg.packages.jetty}/start.jar \
-          --module=deploy,http,jsp \
-          jetty.home=${cfg.packages.jetty} \
-          jetty.base=${cfg.package} \
-          jetty.http.host=${cfg.listenHost} \
-          jetty.http.port=${builtins.toString cfg.listenPort}
-      '';
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        StateDirectory = mkIf (cfg.home == "/var/lib/plantuml") "plantuml";
-        StateDirectoryMode = mkIf (cfg.home == "/var/lib/plantuml") "0750";
-
-        # Hardening
-        AmbientCapabilities = [ "" ];
-        CapabilityBoundingSet = [ "" ];
-        DynamicUser = true;
-        LockPersonality = true;
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateNetwork = false;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectSystem = "strict";
-        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" ];
-      };
-    };
-  };
-
-  meta.maintainers = with lib.maintainers; [ truh anthonyroussel ];
-}
diff --git a/nixos/modules/services/web-apps/plausible.md b/nixos/modules/services/web-apps/plausible.md
deleted file mode 100644
index d3673eabddd4..000000000000
--- a/nixos/modules/services/web-apps/plausible.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Plausible {#module-services-plausible}
-
-[Plausible](https://plausible.io/) is a privacy-friendly alternative to
-Google analytics.
-
-## Basic Usage {#module-services-plausible-basic-usage}
-
-At first, a secret key is needed to be generated. This can be done with e.g.
-```ShellSession
-$ openssl rand -base64 64
-```
-
-After that, `plausible` can be deployed like this:
-```nix
-{
-  services.plausible = {
-    enable = true;
-    adminUser = {
-      # activate is used to skip the email verification of the admin-user that's
-      # automatically created by plausible. This is only supported if
-      # postgresql is configured by the module. This is done by default, but
-      # can be turned off with services.plausible.database.postgres.setup.
-      activate = true;
-      email = "admin@localhost";
-      passwordFile = "/run/secrets/plausible-admin-pwd";
-    };
-    server = {
-      baseUrl = "http://analytics.example.org";
-      # secretKeybaseFile is a path to the file which contains the secret generated
-      # with openssl as described above.
-      secretKeybaseFile = "/run/secrets/plausible-secret-key-base";
-    };
-  };
-}
-```
diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix
deleted file mode 100644
index 1f909bbd67a3..000000000000
--- a/nixos/modules/services/web-apps/plausible.nix
+++ /dev/null
@@ -1,334 +0,0 @@
-{ lib, pkgs, config, ... }:
-
-with lib;
-
-let
-  cfg = config.services.plausible;
-
-in {
-  options.services.plausible = {
-    enable = mkEnableOption "plausible";
-
-    package = mkPackageOption pkgs "plausible" { };
-
-    adminUser = {
-      name = mkOption {
-        default = "admin";
-        type = types.str;
-        description = ''
-          Name of the admin user that plausible will created on initial startup.
-        '';
-      };
-
-      email = mkOption {
-        type = types.str;
-        example = "admin@localhost";
-        description = ''
-          Email-address of the admin-user.
-        '';
-      };
-
-      passwordFile = mkOption {
-        type = types.either types.str types.path;
-        description = ''
-          Path to the file which contains the password of the admin user.
-        '';
-      };
-
-      activate = mkEnableOption "activating the freshly created admin-user";
-    };
-
-    database = {
-      clickhouse = {
-        setup = mkEnableOption "creating a clickhouse instance" // { default = true; };
-        url = mkOption {
-          default = "http://localhost:8123/default";
-          type = types.str;
-          description = ''
-            The URL to be used to connect to `clickhouse`.
-          '';
-        };
-      };
-      postgres = {
-        setup = mkEnableOption "creating a postgresql instance" // { default = true; };
-        dbname = mkOption {
-          default = "plausible";
-          type = types.str;
-          description = ''
-            Name of the database to use.
-          '';
-        };
-        socket = mkOption {
-          default = "/run/postgresql";
-          type = types.str;
-          description = ''
-            Path to the UNIX domain-socket to communicate with `postgres`.
-          '';
-        };
-      };
-    };
-
-    server = {
-      disableRegistration = mkOption {
-        default = true;
-        type = types.enum [true false "invite_only"];
-        description = ''
-          Whether to prohibit creating an account in plausible's UI or allow on `invite_only`.
-        '';
-      };
-      secretKeybaseFile = mkOption {
-        type = types.either types.path types.str;
-        description = ''
-          Path to the secret used by the `phoenix`-framework. Instructions
-          how to generate one are documented in the
-          [
-          framework docs](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content).
-        '';
-      };
-      listenAddress = mkOption {
-        default = "127.0.0.1";
-        type = types.str;
-        description = ''
-          The IP address on which the server is listening.
-        '';
-      };
-      port = mkOption {
-        default = 8000;
-        type = types.port;
-        description = ''
-          Port where the service should be available.
-        '';
-      };
-      baseUrl = mkOption {
-        type = types.str;
-        description = ''
-          Public URL where plausible is available.
-
-          Note that `/path` components are currently ignored:
-          [
-            https://github.com/plausible/analytics/issues/1182
-          ](https://github.com/plausible/analytics/issues/1182).
-        '';
-      };
-    };
-
-    mail = {
-      email = mkOption {
-        default = "hello@plausible.local";
-        type = types.str;
-        description = ''
-          The email id to use for as *from* address of all communications
-          from Plausible.
-        '';
-      };
-      smtp = {
-        hostAddr = mkOption {
-          default = "localhost";
-          type = types.str;
-          description = ''
-            The host address of your smtp server.
-          '';
-        };
-        hostPort = mkOption {
-          default = 25;
-          type = types.port;
-          description = ''
-            The port of your smtp server.
-          '';
-        };
-        user = mkOption {
-          default = null;
-          type = types.nullOr types.str;
-          description = ''
-            The username/email in case SMTP auth is enabled.
-          '';
-        };
-        passwordFile = mkOption {
-          default = null;
-          type = with types; nullOr (either str path);
-          description = ''
-            The path to the file with the password in case SMTP auth is enabled.
-          '';
-        };
-        enableSSL = mkEnableOption "SSL when connecting to the SMTP server";
-        retries = mkOption {
-          type = types.ints.unsigned;
-          default = 2;
-          description = ''
-            Number of retries to make until mailer gives up.
-          '';
-        };
-      };
-    };
-  };
-
-  imports = [
-    (mkRemovedOptionModule [ "services" "plausible" "releaseCookiePath" ] "Plausible uses no distributed Erlang features, so this option is no longer necessary and was removed")
-  ];
-
-  config = mkIf cfg.enable {
-    assertions = [
-      { assertion = cfg.adminUser.activate -> cfg.database.postgres.setup;
-        message = ''
-          Unable to automatically activate the admin-user if no locally managed DB for
-          postgres (`services.plausible.database.postgres.setup') is enabled!
-        '';
-      }
-    ];
-
-    services.postgresql = mkIf cfg.database.postgres.setup {
-      enable = true;
-    };
-
-    services.clickhouse = mkIf cfg.database.clickhouse.setup {
-      enable = true;
-    };
-
-    environment.systemPackages = [ cfg.package ];
-
-    systemd.services = mkMerge [
-      {
-        plausible = {
-          inherit (cfg.package.meta) description;
-          documentation = [ "https://plausible.io/docs/self-hosting" ];
-          wantedBy = [ "multi-user.target" ];
-          after = optional cfg.database.clickhouse.setup "clickhouse.service"
-          ++ optionals cfg.database.postgres.setup [
-              "postgresql.service"
-              "plausible-postgres.service"
-            ];
-          requires = optional cfg.database.clickhouse.setup "clickhouse.service"
-            ++ optionals cfg.database.postgres.setup [
-              "postgresql.service"
-              "plausible-postgres.service"
-            ];
-
-          environment = {
-            # NixOS specific option to avoid that it's trying to write into its store-path.
-            # See also https://github.com/lau/tzdata#data-directory-and-releases
-            STORAGE_DIR = "/var/lib/plausible/elixir_tzdata";
-
-            # Configuration options from
-            # https://plausible.io/docs/self-hosting-configuration
-            PORT = toString cfg.server.port;
-            LISTEN_IP = cfg.server.listenAddress;
-
-            # Note [plausible-needs-no-erlang-distributed-features]:
-            # Plausible does not use, and does not plan to use, any of
-            # Erlang's distributed features, see:
-            #     https://github.com/plausible/analytics/pull/1190#issuecomment-1018820934
-            # Thus, disable distribution for improved simplicity and security:
-            #
-            # When distribution is enabled,
-            # Elixir spwans the Erlang VM, which will listen by default on all
-            # interfaces for messages between Erlang nodes (capable of
-            # remote code execution); it can be protected by a cookie; see
-            # https://erlang.org/doc/reference_manual/distributed.html#security).
-            #
-            # It would be possible to restrict the interface to one of our choice
-            # (e.g. localhost or a VPN IP) similar to how we do it with `listenAddress`
-            # for the Plausible web server; if distribution is ever needed in the future,
-            # https://github.com/NixOS/nixpkgs/pull/130297 shows how to do it.
-            #
-            # But since Plausible does not use this feature in any way,
-            # we just disable it.
-            RELEASE_DISTRIBUTION = "none";
-            # Additional safeguard, in case `RELEASE_DISTRIBUTION=none` ever
-            # stops disabling the start of EPMD.
-            ERL_EPMD_ADDRESS = "127.0.0.1";
-
-            DISABLE_REGISTRATION = if isBool cfg.server.disableRegistration then boolToString cfg.server.disableRegistration else cfg.server.disableRegistration;
-
-            RELEASE_TMP = "/var/lib/plausible/tmp";
-            # Home is needed to connect to the node with iex
-            HOME = "/var/lib/plausible";
-
-            ADMIN_USER_NAME = cfg.adminUser.name;
-            ADMIN_USER_EMAIL = cfg.adminUser.email;
-
-            DATABASE_SOCKET_DIR = cfg.database.postgres.socket;
-            DATABASE_NAME = cfg.database.postgres.dbname;
-            CLICKHOUSE_DATABASE_URL = cfg.database.clickhouse.url;
-
-            BASE_URL = cfg.server.baseUrl;
-
-            MAILER_EMAIL = cfg.mail.email;
-            SMTP_HOST_ADDR = cfg.mail.smtp.hostAddr;
-            SMTP_HOST_PORT = toString cfg.mail.smtp.hostPort;
-            SMTP_RETRIES = toString cfg.mail.smtp.retries;
-            SMTP_HOST_SSL_ENABLED = boolToString cfg.mail.smtp.enableSSL;
-
-            SELFHOST = "true";
-          } // (optionalAttrs (cfg.mail.smtp.user != null) {
-            SMTP_USER_NAME = cfg.mail.smtp.user;
-          });
-
-          path = [ cfg.package ]
-            ++ optional cfg.database.postgres.setup config.services.postgresql.package;
-          script = ''
-            # Elixir does not start up if `RELEASE_COOKIE` is not set,
-            # even though we set `RELEASE_DISTRIBUTION=none` so the cookie should be unused.
-            # Thus, make a random one, which should then be ignored.
-            export RELEASE_COOKIE=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 20)
-            export ADMIN_USER_PWD="$(< $CREDENTIALS_DIRECTORY/ADMIN_USER_PWD )"
-            export SECRET_KEY_BASE="$(< $CREDENTIALS_DIRECTORY/SECRET_KEY_BASE )"
-
-            ${lib.optionalString (cfg.mail.smtp.passwordFile != null)
-              ''export SMTP_USER_PWD="$(< $CREDENTIALS_DIRECTORY/SMTP_USER_PWD )"''}
-
-            ${lib.optionalString cfg.database.postgres.setup ''
-              # setup
-              ${cfg.package}/createdb.sh
-            ''}
-
-            ${cfg.package}/migrate.sh
-            export IP_GEOLOCATION_DB=${pkgs.dbip-country-lite}/share/dbip/dbip-country-lite.mmdb
-            ${cfg.package}/bin/plausible eval "(Plausible.Release.prepare() ; Plausible.Auth.create_user(\"$ADMIN_USER_NAME\", \"$ADMIN_USER_EMAIL\", \"$ADMIN_USER_PWD\"))"
-            ${optionalString cfg.adminUser.activate ''
-              psql -d plausible <<< "UPDATE users SET email_verified=true where email = '$ADMIN_USER_EMAIL';"
-            ''}
-
-            exec plausible start
-          '';
-
-          serviceConfig = {
-            DynamicUser = true;
-            PrivateTmp = true;
-            WorkingDirectory = "/var/lib/plausible";
-            StateDirectory = "plausible";
-            LoadCredential = [
-              "ADMIN_USER_PWD:${cfg.adminUser.passwordFile}"
-              "SECRET_KEY_BASE:${cfg.server.secretKeybaseFile}"
-            ] ++ lib.optionals (cfg.mail.smtp.passwordFile != null) [ "SMTP_USER_PWD:${cfg.mail.smtp.passwordFile}"];
-          };
-        };
-      }
-      (mkIf cfg.database.postgres.setup {
-        # `plausible' requires the `citext'-extension.
-        plausible-postgres = {
-          after = [ "postgresql.service" ];
-          partOf = [ "plausible.service" ];
-          serviceConfig = {
-            Type = "oneshot";
-            User = config.services.postgresql.superUser;
-            RemainAfterExit = true;
-          };
-          script = with cfg.database.postgres; ''
-            PSQL() {
-              ${config.services.postgresql.package}/bin/psql --port=5432 "$@"
-            }
-            # check if the database already exists
-            if ! PSQL -lqt | ${pkgs.coreutils}/bin/cut -d \| -f 1 | ${pkgs.gnugrep}/bin/grep -qw ${dbname} ; then
-              PSQL -tAc "CREATE ROLE plausible WITH LOGIN;"
-              PSQL -tAc "CREATE DATABASE ${dbname} WITH OWNER plausible;"
-              PSQL -d ${dbname} -tAc "CREATE EXTENSION IF NOT EXISTS citext;"
-            fi
-          '';
-        };
-      })
-    ];
-  };
-
-  meta.maintainers = with maintainers; [ xanderio ];
-  meta.doc = ./plausible.md;
-}
diff --git a/nixos/modules/services/web-apps/powerdns-admin.nix b/nixos/modules/services/web-apps/powerdns-admin.nix
deleted file mode 100644
index d64c468a9cb5..000000000000
--- a/nixos/modules/services/web-apps/powerdns-admin.nix
+++ /dev/null
@@ -1,153 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.powerdns-admin;
-
-  configText = ''
-    ${cfg.config}
-  ''
-  + optionalString (cfg.secretKeyFile != null) ''
-    with open('${cfg.secretKeyFile}') as file:
-      SECRET_KEY = file.read()
-  ''
-  + optionalString (cfg.saltFile != null) ''
-    with open('${cfg.saltFile}') as file:
-      SALT = file.read()
-  '';
-in
-{
-  options.services.powerdns-admin = {
-    enable = mkEnableOption "the PowerDNS web interface";
-
-    extraArgs = mkOption {
-      type = types.listOf types.str;
-      default = [ ];
-      example = literalExpression ''
-        [ "-b" "127.0.0.1:8000" ]
-      '';
-      description = ''
-        Extra arguments passed to powerdns-admin.
-      '';
-    };
-
-    config = mkOption {
-      type = types.str;
-      default = "";
-      example = ''
-        BIND_ADDRESS = '127.0.0.1'
-        PORT = 8000
-        SQLALCHEMY_DATABASE_URI = 'postgresql://powerdnsadmin@/powerdnsadmin?host=/run/postgresql'
-      '';
-      description = ''
-        Configuration python file.
-        See [the example configuration](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/v${pkgs.powerdns-admin.version}/configs/development.py)
-        for options.
-      '';
-    };
-
-    secretKeyFile = mkOption {
-      type = types.nullOr types.path;
-      example = "/etc/powerdns-admin/secret";
-      description = ''
-        The secret used to create cookies.
-        This needs to be set, otherwise the default is used and everyone can forge valid login cookies.
-        Set this to null to ignore this setting and configure it through another way.
-      '';
-    };
-
-    saltFile = mkOption {
-      type = types.nullOr types.path;
-      example = "/etc/powerdns-admin/salt";
-      description = ''
-        The salt used for serialization.
-        This should be set, otherwise the default is used.
-        Set this to null to ignore this setting and configure it through another way.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.powerdns-admin = {
-      description = "PowerDNS web interface";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "networking.target" ];
-
-      environment.FLASK_CONF = builtins.toFile "powerdns-admin-config.py" configText;
-      environment.PYTHONPATH = pkgs.powerdns-admin.pythonPath;
-      serviceConfig = {
-        ExecStart = "${pkgs.powerdns-admin}/bin/powerdns-admin --pid /run/powerdns-admin/pid ${escapeShellArgs cfg.extraArgs}";
-        # Set environment variables only for starting flask database upgrade
-        ExecStartPre = "${pkgs.coreutils}/bin/env FLASK_APP=${pkgs.powerdns-admin}/share/powerdnsadmin/__init__.py SESSION_TYPE= ${pkgs.python3Packages.flask}/bin/flask db upgrade -d ${pkgs.powerdns-admin}/share/migrations";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        ExecStop = "${pkgs.coreutils}/bin/kill -TERM $MAINPID";
-        PIDFile = "/run/powerdns-admin/pid";
-        RuntimeDirectory = "powerdns-admin";
-        User = "powerdnsadmin";
-        Group = "powerdnsadmin";
-
-        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
-        BindReadOnlyPaths = [
-          "/nix/store"
-          "-/etc/resolv.conf"
-          "-/etc/nsswitch.conf"
-          "-/etc/hosts"
-          "-/etc/localtime"
-        ]
-        ++ (optional (cfg.secretKeyFile != null) cfg.secretKeyFile)
-        ++ (optional (cfg.saltFile != null) cfg.saltFile);
-        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
-        # ProtectClock= adds DeviceAllow=char-rtc r
-        DeviceAllow = "";
-        # Implies ProtectSystem=strict, which re-mounts all paths
-        #DynamicUser = true;
-        LockPersonality = true;
-        MemoryDenyWriteExecute = true;
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateMounts = true;
-        # Needs to start a server
-        #PrivateNetwork = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        ProcSubset = "pid";
-        ProtectClock = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        # Would re-mount paths ignored by temporary root
-        #ProtectSystem = "strict";
-        ProtectControlGroups = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SystemCallArchitectures = "native";
-        # gunicorn needs setuid
-        SystemCallFilter = [
-          "@system-service"
-          "~@privileged @resources @keyring"
-          # These got removed by the line above but are needed
-          "@setuid @chown"
-        ];
-        TemporaryFileSystem = "/:ro";
-        # Does not work well with the temporary root
-        #UMask = "0066";
-      };
-    };
-
-    users.groups.powerdnsadmin = { };
-    users.users.powerdnsadmin = {
-      description = "PowerDNS web interface user";
-      isSystemUser = true;
-      group = "powerdnsadmin";
-    };
-  };
-
-  # uses attributes of the linked package
-  meta.buildDocsInSandbox = false;
-}
diff --git a/nixos/modules/services/web-apps/pretalx.nix b/nixos/modules/services/web-apps/pretalx.nix
deleted file mode 100644
index 1411d11982c8..000000000000
--- a/nixos/modules/services/web-apps/pretalx.nix
+++ /dev/null
@@ -1,471 +0,0 @@
-{ config
-, lib
-, pkgs
-, utils
-, ...
-}:
-
-let
-  cfg = config.services.pretalx;
-  format = pkgs.formats.ini { };
-
-  configFile = format.generate "pretalx.cfg" cfg.settings;
-
-  finalPackage = cfg.package.override {
-    inherit (cfg) plugins;
-  };
-
-  pythonEnv = finalPackage.python.buildEnv.override {
-    extraLibs = with finalPackage.python.pkgs; [
-      (toPythonModule finalPackage)
-      gunicorn
-    ]
-    ++ finalPackage.optional-dependencies.redis
-    ++ lib.optionals cfg.celery.enable [ celery ]
-    ++ lib.optionals (cfg.settings.database.backend == "mysql") finalPackage.optional-dependencies.mysql
-    ++ lib.optionals (cfg.settings.database.backend == "postgresql") finalPackage.optional-dependencies.postgres;
-  };
-in
-
-{
-  meta = with lib; {
-    maintainers = with maintainers; [ hexa] ++ teams.c3d2.members;
-  };
-
-  options.services.pretalx = {
-    enable = lib.mkEnableOption "pretalx";
-
-    package = lib.mkPackageOptionMD pkgs "pretalx" {};
-
-    group = lib.mkOption {
-      type = lib.types.str;
-      default = "pretalx";
-      description = "Group under which pretalx should run.";
-    };
-
-    user = lib.mkOption {
-      type = lib.types.str;
-      default = "pretalx";
-      description = "User under which pretalx should run.";
-    };
-
-    plugins = lib.mkOption {
-      type = with lib.types; listOf package;
-      default = [];
-      example = lib.literalExpression ''
-        with config.services.pretalx.package.plugins; [
-          pages
-          youtube
-        ];
-      '';
-      description = ''
-        Pretalx plugins to install into the Python environment.
-      '';
-    };
-
-    gunicorn.extraArgs = lib.mkOption {
-      type = with lib.types; listOf str;
-      default = [
-        "--name=pretalx"
-      ];
-      example = [
-        "--name=pretalx"
-        "--workers=4"
-        "--max-requests=1200"
-        "--max-requests-jitter=50"
-        "--log-level=info"
-      ];
-      description = ''
-        Extra arguments to pass to gunicorn.
-        See <https://docs.pretalx.org/administrator/installation.html#step-6-starting-pretalx-as-a-service> for details.
-      '';
-      apply = lib.escapeShellArgs;
-    };
-
-    celery = {
-      enable = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        example = false;
-        description = ''
-          Whether to set up celery as an asynchronous task runner.
-        '';
-      };
-
-      extraArgs = lib.mkOption {
-        type = with lib.types; listOf str;
-        default = [ ];
-        description = ''
-          Extra arguments to pass to celery.
-
-          See <https://docs.celeryq.dev/en/stable/reference/cli.html#celery-worker> for more info.
-        '';
-        apply = utils.escapeSystemdExecArgs;
-      };
-    };
-
-    nginx = {
-      enable = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        example = false;
-        description = ''
-          Whether to set up an nginx virtual host.
-        '';
-      };
-
-      domain = lib.mkOption {
-        type = lib.types.str;
-        example = "talks.example.com";
-        description = ''
-          The domain name under which to set up the virtual host.
-        '';
-      };
-    };
-
-    database.createLocally = lib.mkOption {
-      type = lib.types.bool;
-      default = true;
-      example = false;
-      description = ''
-        Whether to automatically set up the database on the local DBMS instance.
-
-        Currently only supported for PostgreSQL. Not required for sqlite.
-      '';
-    };
-
-    settings = lib.mkOption {
-      type = lib.types.submodule {
-        freeformType = format.type;
-        options = {
-          database = {
-            backend = lib.mkOption {
-              type = lib.types.enum [
-                "postgresql"
-              ];
-              default = "postgresql";
-              description = ''
-                Database backend to use.
-
-                Currently only PostgreSQL gets tested, and as such we don't support any other DBMS.
-              '';
-              readOnly = true; # only postgres supported right now
-            };
-
-            host = lib.mkOption {
-              type = with lib.types; nullOr types.path;
-              default = if cfg.settings.database.backend == "postgresql" then "/run/postgresql"
-                else if cfg.settings.database.backend == "mysql" then "/run/mysqld/mysqld.sock"
-                else null;
-              defaultText = lib.literalExpression ''
-                if config.services.pretalx.settings..database.backend == "postgresql" then "/run/postgresql"
-                else if config.services.pretalx.settings.database.backend == "mysql" then "/run/mysqld/mysqld.sock"
-                else null
-              '';
-              description = ''
-                Database host or socket path.
-              '';
-            };
-
-            name = lib.mkOption {
-              type = lib.types.str;
-              default = "pretalx";
-              description = ''
-                Database name.
-              '';
-            };
-
-            user = lib.mkOption {
-              type = lib.types.str;
-              default = "pretalx";
-              description = ''
-                Database username.
-              '';
-            };
-          };
-
-          filesystem = {
-            data = lib.mkOption {
-              type = lib.types.path;
-              default = "/var/lib/pretalx";
-              description = ''
-                Base path for all other storage paths.
-              '';
-            };
-            logs = lib.mkOption {
-              type = lib.types.path;
-              default = "/var/log/pretalx";
-              description = ''
-                Path to the log directory, that pretalx logs message to.
-              '';
-            };
-            static = lib.mkOption {
-              type = lib.types.path;
-              default = "${cfg.package.static}/";
-              defaultText = lib.literalExpression "\${config.services.pretalx.package}.static}/";
-              readOnly = true;
-              description = ''
-                Path to the directory that contains static files.
-              '';
-            };
-          };
-
-          celery = {
-            backend = lib.mkOption {
-              type = with lib.types; nullOr str;
-              default = lib.optionalString cfg.celery.enable "redis+socket://${config.services.redis.servers.pretalx.unixSocket}?virtual_host=1";
-              defaultText = lib.literalExpression ''
-                optionalString config.services.pretalx.celery.enable "redis+socket://''${config.services.redis.servers.pretalx.unixSocket}?virtual_host=1"
-              '';
-              description = ''
-                URI to the celery backend used for the asynchronous job queue.
-              '';
-            };
-
-            broker = lib.mkOption {
-              type = with lib.types; nullOr str;
-              default = lib.optionalString cfg.celery.enable "redis+socket://${config.services.redis.servers.pretalx.unixSocket}?virtual_host=2";
-              defaultText = lib.literalExpression ''
-                optionalString config.services.pretalx.celery.enable "redis+socket://''${config.services.redis.servers.pretalx.unixSocket}?virtual_host=2"
-              '';
-              description = ''
-                URI to the celery broker used for the asynchronous job queue.
-              '';
-            };
-          };
-
-          redis = {
-            location = lib.mkOption {
-              type = with lib.types; nullOr str;
-              default = "unix://${config.services.redis.servers.pretalx.unixSocket}?db=0";
-              defaultText = lib.literalExpression ''
-                "unix://''${config.services.redis.servers.pretalx.unixSocket}?db=0"
-              '';
-              description = ''
-                URI to the redis server, used to speed up locking, caching and session storage.
-              '';
-            };
-
-            session = lib.mkOption {
-              type = lib.types.bool;
-              default = true;
-              example = false;
-              description = ''
-                Whether to use redis as the session storage.
-              '';
-            };
-          };
-
-          site = {
-            url = lib.mkOption {
-              type = lib.types.str;
-              default = "https://${cfg.nginx.domain}";
-              defaultText = lib.literalExpression "https://\${config.services.pretalx.nginx.domain}";
-              example = "https://talks.example.com";
-              description = ''
-                The base URI below which your pretalx instance will be reachable.
-              '';
-            };
-          };
-        };
-      };
-      default = { };
-      description = ''
-        pretalx configuration as a Nix attribute set. All settings can also be passed
-        from the environment.
-
-        See <https://docs.pretalx.org/administrator/configure.html> for possible options.
-      '';
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    # https://docs.pretalx.org/administrator/installation.html
-
-    environment.systemPackages = [
-      (pkgs.writeScriptBin "pretalx-manage" ''
-        cd ${cfg.settings.filesystem.data}
-        sudo=exec
-        if [[ "$USER" != ${cfg.user} ]]; then
-          sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env=PRETALX_CONFIG_FILE'
-        fi
-        export PRETALX_CONFIG_FILE=${configFile}
-        $sudo ${lib.getExe' pythonEnv "pretalx-manage"} "$@"
-      '')
-    ];
-
-    services = {
-      nginx = lib.mkIf cfg.nginx.enable {
-        enable = true;
-        recommendedGzipSettings = lib.mkDefault true;
-        recommendedOptimisation = lib.mkDefault true;
-        recommendedProxySettings = lib.mkDefault true;
-        recommendedTlsSettings = lib.mkDefault true;
-        upstreams.pretalx.servers."unix:/run/pretalx/pretalx.sock" = { };
-        virtualHosts.${cfg.nginx.domain} = {
-          # https://docs.pretalx.org/administrator/installation.html#step-7-ssl
-          extraConfig = ''
-            more_set_headers "Referrer-Policy: same-origin";
-            more_set_headers "X-Content-Type-Options: nosniff";
-          '';
-          locations = {
-            "/".proxyPass = "http://pretalx";
-            "/media/" = {
-              alias = "${cfg.settings.filesystem.data}/media/";
-              extraConfig = ''
-                access_log off;
-                more_set_headers 'Content-Disposition: attachment; filename="$1"';
-                expires 7d;
-              '';
-            };
-            "/static/" = {
-              alias = cfg.settings.filesystem.static;
-              extraConfig = ''
-                access_log off;
-                more_set_headers Cache-Control "public";
-                expires 365d;
-              '';
-            };
-          };
-        };
-      };
-
-      postgresql = lib.mkIf (cfg.database.createLocally && cfg.settings.database.backend == "postgresql") {
-        enable = true;
-        ensureUsers = [ {
-          name = cfg.settings.database.user;
-          ensureDBOwnership = true;
-        } ];
-        ensureDatabases = [ cfg.settings.database.name ];
-      };
-
-      redis.servers.pretalx.enable = true;
-    };
-
-    systemd.services = let
-      commonUnitConfig = {
-        environment.PRETALX_CONFIG_FILE = configFile;
-        serviceConfig = {
-          User = "pretalx";
-          Group = "pretalx";
-          StateDirectory = [
-            "pretalx"
-            "pretalx/media"
-          ];
-          StateDirectoryMode = "0750";
-          LogsDirectory = "pretalx";
-          WorkingDirectory = cfg.settings.filesystem.data;
-          SupplementaryGroups = [ "redis-pretalx" ];
-          AmbientCapabilities = "";
-          CapabilityBoundingSet = [ "" ];
-          DevicePolicy = "closed";
-          LockPersonality = true;
-          MemoryDenyWriteExecute = true;
-          NoNewPrivileges = true;
-          PrivateDevices = true;
-          PrivateTmp = true;
-          ProcSubset = "pid";
-          ProtectControlGroups = true;
-          ProtectHome = true;
-          ProtectHostname = true;
-          ProtectKernelLogs = true;
-          ProtectKernelModules = true;
-          ProtectKernelTunables = true;
-          ProtectProc = "invisible";
-          ProtectSystem = "strict";
-          RemoveIPC = true;
-          RestrictAddressFamilies = [
-            "AF_INET"
-            "AF_INET6"
-            "AF_UNIX"
-          ];
-          RestrictNamespaces = true;
-          RestrictRealtime = true;
-          RestrictSUIDSGID = true;
-          SystemCallArchitectures = "native";
-          SystemCallFilter = [
-            "@system-service"
-            "~@privileged"
-            "@chown"
-          ];
-          UMask = "0027";
-        };
-      };
-    in {
-      pretalx-web = lib.recursiveUpdate commonUnitConfig {
-        description = "pretalx web service";
-        after = [
-          "network.target"
-          "redis-pretalx.service"
-        ] ++ lib.optionals (cfg.settings.database.backend == "postgresql") [
-          "postgresql.service"
-        ] ++ lib.optionals (cfg.settings.database.backend == "mysql") [
-          "mysql.service"
-        ];
-        wantedBy = [ "multi-user.target" ];
-        preStart = ''
-          versionFile="${cfg.settings.filesystem.data}/.version"
-          version=$(cat "$versionFile" 2>/dev/null || echo 0)
-
-          if [[ $version != ${cfg.package.version} ]]; then
-            ${lib.getExe' pythonEnv "pretalx-manage"} migrate
-
-            echo "${cfg.package.version}" > "$versionFile"
-          fi
-        '';
-        serviceConfig = {
-          ExecStart = "${lib.getExe' pythonEnv "gunicorn"} --bind unix:/run/pretalx/pretalx.sock ${cfg.gunicorn.extraArgs} pretalx.wsgi";
-          RuntimeDirectory = "pretalx";
-        };
-      };
-
-      pretalx-periodic = lib.recursiveUpdate commonUnitConfig {
-        description = "pretalx periodic task runner";
-        # every 15 minutes
-        startAt = [ "*:3,18,33,48" ];
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = "${lib.getExe' pythonEnv "pretalx-manage"} runperiodic";
-        };
-      };
-
-      pretalx-clear-sessions = lib.recursiveUpdate commonUnitConfig {
-        description = "pretalx session pruning";
-        startAt = [ "monthly" ];
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = "${lib.getExe' pythonEnv "pretalx-manage"} clearsessions";
-        };
-      };
-
-      pretalx-worker = lib.mkIf cfg.celery.enable (lib.recursiveUpdate commonUnitConfig {
-        description = "pretalx asynchronous job runner";
-        after = [
-          "network.target"
-          "redis-pretalx.service"
-        ] ++ lib.optionals (cfg.settings.database.backend == "postgresql") [
-          "postgresql.service"
-        ] ++ lib.optionals (cfg.settings.database.backend == "mysql") [
-          "mysql.service"
-        ];
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig.ExecStart = "${lib.getExe' pythonEnv "celery"} -A pretalx.celery_app worker ${cfg.celery.extraArgs}";
-      });
-
-      nginx.serviceConfig.SupplementaryGroups = lib.mkIf cfg.nginx.enable [ "pretalx" ];
-    };
-
-    systemd.sockets.pretalx-web.socketConfig = {
-      ListenStream = "/run/pretalx/pretalx.sock";
-      SocketUser = "nginx";
-    };
-
-    users = {
-      groups.${cfg.group} = {};
-      users.${cfg.user} = {
-        isSystemUser = true;
-        inherit (cfg) group;
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/pretix.nix b/nixos/modules/services/web-apps/pretix.nix
deleted file mode 100644
index 9786b6116026..000000000000
--- a/nixos/modules/services/web-apps/pretix.nix
+++ /dev/null
@@ -1,581 +0,0 @@
-{ config
-, lib
-, pkgs
-, utils
-, ...
-}:
-
-let
-  inherit (lib)
-    concatMapStringsSep
-    escapeShellArgs
-    filter
-    filterAttrs
-    getExe
-    getExe'
-    isAttrs
-    isList
-    literalExpression
-    mapAttrs
-    mkDefault
-    mkEnableOption
-    mkIf
-    mkOption
-    mkPackageOption
-    optionals
-    optionalString
-    recursiveUpdate
-    types
-  ;
-
-  filterRecursiveNull = o:
-    if isAttrs o then
-      mapAttrs (_: v: filterRecursiveNull v) (filterAttrs (_: v: v != null) o)
-    else if isList o then
-      map filterRecursiveNull (filter (v: v != null) o)
-    else
-      o;
-
-  cfg = config.services.pretix;
-  format = pkgs.formats.ini { };
-
-  configFile = format.generate "pretix.cfg" (filterRecursiveNull cfg.settings);
-
-  finalPackage = cfg.package.override {
-    inherit (cfg) plugins;
-  };
-
-  pythonEnv = cfg.package.python.buildEnv.override {
-    extraLibs = with cfg.package.python.pkgs; [
-      (toPythonModule finalPackage)
-      gunicorn
-    ]
-    ++ lib.optionals (cfg.settings.memcached.location != null)
-      cfg.package.optional-dependencies.memcached
-    ;
-  };
-
-  withRedis = cfg.settings.redis.location != null;
-in
-{
-  meta = with lib; {
-    maintainers = with maintainers; [ hexa ];
-  };
-
-  options.services.pretix = {
-    enable = mkEnableOption "Pretix, a ticket shop application for conferences, festivals, concerts, etc.";
-
-    package = mkPackageOption pkgs "pretix" { };
-
-    group = mkOption {
-      type = types.str;
-      default = "pretix";
-      description = ''
-        Group under which pretix should run.
-      '';
-    };
-
-    user = mkOption {
-      type = types.str;
-      default = "pretix";
-      description = ''
-        User under which pretix should run.
-      '';
-    };
-
-    environmentFile = mkOption {
-      type = types.nullOr types.path;
-      default = null;
-      example = "/run/keys/pretix-secrets.env";
-      description = ''
-        Environment file to pass secret configuration values.
-
-        Each line must follow the `PRETIX_SECTION_KEY=value` pattern.
-      '';
-    };
-
-    plugins = mkOption {
-      type = types.listOf types.package;
-      default = [];
-      example = literalExpression ''
-        with config.services.pretix.package.plugins; [
-          passbook
-          pages
-        ];
-      '';
-      description = ''
-        Pretix plugins to install into the Python environment.
-      '';
-    };
-
-    gunicorn.extraArgs = mkOption {
-      type = with types; listOf str;
-      default = [
-        "--name=pretix"
-      ];
-      example = [
-        "--name=pretix"
-        "--workers=4"
-        "--max-requests=1200"
-        "--max-requests-jitter=50"
-        "--log-level=info"
-      ];
-      description = ''
-        Extra arguments to pass to gunicorn.
-        See <https://docs.pretix.eu/en/latest/admin/installation/manual_smallscale.html#start-pretix-as-a-service> for details.
-      '';
-      apply = escapeShellArgs;
-    };
-
-    celery = {
-      extraArgs = mkOption {
-        type = with types; listOf str;
-        default = [ ];
-        description = ''
-          Extra arguments to pass to celery.
-
-          See <https://docs.celeryq.dev/en/stable/reference/cli.html#celery-worker> for more info.
-        '';
-        apply = utils.escapeSystemdExecArgs;
-      };
-    };
-
-    nginx = {
-      enable = mkOption {
-        type = types.bool;
-        default = true;
-        example = false;
-        description = ''
-          Whether to set up an nginx virtual host.
-        '';
-      };
-
-      domain = mkOption {
-        type = types.str;
-        example = "talks.example.com";
-        description = ''
-          The domain name under which to set up the virtual host.
-        '';
-      };
-    };
-
-    database.createLocally = mkOption {
-      type = types.bool;
-      default = true;
-      example = false;
-      description = ''
-        Whether to automatically set up the database on the local DBMS instance.
-
-        Only supported for PostgreSQL. Not required for sqlite.
-      '';
-    };
-
-    settings = mkOption {
-      type = types.submodule {
-        freeformType = format.type;
-        options = {
-          pretix = {
-            instance_name = mkOption {
-              type = types.str;
-              example = "tickets.example.com";
-              description = ''
-                The name of this installation.
-              '';
-            };
-
-            url = mkOption {
-              type = types.str;
-              example = "https://tickets.example.com";
-              description = ''
-                The installation’s full URL, without a trailing slash.
-              '';
-            };
-
-            cachedir = mkOption {
-              type = types.path;
-              default = "/var/cache/pretix";
-              description = ''
-                Directory for storing temporary files.
-              '';
-            };
-
-            datadir = mkOption {
-              type = types.path;
-              default = "/var/lib/pretix";
-              description = ''
-                Directory for storing user uploads and similar data.
-              '';
-            };
-
-            logdir = mkOption {
-              type = types.path;
-              default = "/var/log/pretix";
-              description = ''
-                Directory for storing log files.
-              '';
-            };
-
-            currency = mkOption {
-              type = types.str;
-              default = "EUR";
-              example = "USD";
-              description = ''
-                Default currency for events in its ISO 4217 three-letter code.
-              '';
-            };
-
-            registration = mkOption {
-              type = types.bool;
-              default = false;
-              example = true;
-              description = ''
-                Whether to allow registration of new admin users.
-              '';
-            };
-          };
-
-          database = {
-            backend = mkOption {
-              type = types.enum [
-                "sqlite3"
-                "postgresql"
-              ];
-              default = "postgresql";
-              description = ''
-                Database backend to use.
-
-                Only postgresql is recommended for production setups.
-              '';
-            };
-
-            host = mkOption {
-              type = with types; nullOr types.path;
-              default = if cfg.settings.database.backend == "postgresql" then "/run/postgresql" else null;
-              defaultText = literalExpression ''
-                if config.services.pretix.settings..database.backend == "postgresql" then "/run/postgresql"
-                else null
-              '';
-              description = ''
-                Database host or socket path.
-              '';
-            };
-
-            name = mkOption {
-              type = types.str;
-              default = "pretix";
-              description = ''
-                Database name.
-              '';
-            };
-
-            user = mkOption {
-              type = types.str;
-              default = "pretix";
-              description = ''
-                Database username.
-              '';
-            };
-          };
-
-          mail = {
-            from = mkOption {
-              type = types.str;
-              example = "tickets@example.com";
-              description = ''
-                E-Mail address used in the `FROM` header of outgoing mails.
-              '';
-            };
-
-            host = mkOption {
-              type = types.str;
-              default = "localhost";
-              example = "mail.example.com";
-              description = ''
-                Hostname of the SMTP server use for mail delivery.
-              '';
-            };
-
-            port = mkOption {
-              type = types.port;
-              default = 25;
-              example = 587;
-              description = ''
-                Port of the SMTP server to use for mail delivery.
-              '';
-            };
-          };
-
-          celery = {
-            backend = mkOption {
-              type = types.str;
-              default = "redis+socket://${config.services.redis.servers.pretix.unixSocket}?virtual_host=1";
-              defaultText = literalExpression ''
-                redis+socket://''${config.services.redis.servers.pretix.unixSocket}?virtual_host=1
-              '';
-              description = ''
-                URI to the celery backend used for the asynchronous job queue.
-              '';
-            };
-
-            broker = mkOption {
-              type = types.str;
-              default = "redis+socket://${config.services.redis.servers.pretix.unixSocket}?virtual_host=2";
-              defaultText = literalExpression ''
-                redis+socket://''${config.services.redis.servers.pretix.unixSocket}?virtual_host=2
-              '';
-              description = ''
-                URI to the celery broker used for the asynchronous job queue.
-              '';
-            };
-          };
-
-          redis = {
-            location = mkOption {
-              type = with types; nullOr str;
-              default = "unix://${config.services.redis.servers.pretix.unixSocket}?db=0";
-              defaultText = literalExpression ''
-                "unix://''${config.services.redis.servers.pretix.unixSocket}?db=0"
-              '';
-              description = ''
-                URI to the redis server, used to speed up locking, caching and session storage.
-              '';
-            };
-
-            sessions = mkOption {
-              type = types.bool;
-              default = true;
-              example = false;
-              description = ''
-                Whether to use redis as the session storage.
-              '';
-            };
-          };
-
-          memcached = {
-            location = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              example = "127.0.0.1:11211";
-              description = ''
-                The `host:port` combination or the path to the UNIX socket of a memcached instance.
-
-                Can be used instead of Redis for caching.
-              '';
-            };
-          };
-
-          tools = {
-            pdftk = mkOption {
-              type = types.path;
-              default = getExe pkgs.pdftk;
-              defaultText = literalExpression ''
-                lib.getExe pkgs.pdftk
-              '';
-              description = ''
-                Path to the pdftk executable.
-              '';
-            };
-          };
-        };
-      };
-      default = { };
-      description = ''
-        pretix configuration as a Nix attribute set. All settings can also be passed
-        from the environment.
-
-        See <https://docs.pretix.eu/en/latest/admin/config.html> for possible options.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    # https://docs.pretix.eu/en/latest/admin/installation/index.html
-
-    environment.systemPackages = [
-      (pkgs.writeScriptBin "pretix-manage" ''
-        cd ${cfg.settings.pretix.datadir}
-        sudo=exec
-        if [[ "$USER" != ${cfg.user} ]]; then
-          sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} ${optionalString withRedis "-g redis-pretix"} --preserve-env=PRETIX_CONFIG_FILE'
-        fi
-        export PRETIX_CONFIG_FILE=${configFile}
-        $sudo ${getExe' pythonEnv "pretix-manage"} "$@"
-      '')
-    ];
-
-    services = {
-      nginx = mkIf cfg.nginx.enable {
-        enable = true;
-        recommendedGzipSettings = mkDefault true;
-        recommendedOptimisation = mkDefault true;
-        recommendedProxySettings = mkDefault true;
-        recommendedTlsSettings = mkDefault true;
-        upstreams.pretix.servers."unix:/run/pretix/pretix.sock" = { };
-        virtualHosts.${cfg.nginx.domain} = {
-          # https://docs.pretix.eu/en/latest/admin/installation/manual_smallscale.html#ssl
-          extraConfig = ''
-            more_set_headers Referrer-Policy same-origin;
-            more_set_headers X-Content-Type-Options nosniff;
-          '';
-          locations = {
-            "/".proxyPass = "http://pretix";
-            "/media/" = {
-              alias = "${cfg.settings.pretix.datadir}/media/";
-              extraConfig = ''
-                access_log off;
-                expires 7d;
-              '';
-            };
-            "^~ /media/(cachedfiles|invoices)" = {
-              extraConfig = ''
-                deny all;
-                return 404;
-              '';
-            };
-            "/static/" = {
-              alias = "${finalPackage}/${cfg.package.python.sitePackages}/pretix/static.dist/";
-              extraConfig = ''
-                access_log off;
-                more_set_headers Cache-Control "public";
-                expires 365d;
-              '';
-            };
-          };
-        };
-      };
-
-      postgresql = mkIf (cfg.database.createLocally && cfg.settings.database.backend == "postgresql") {
-        enable = true;
-        ensureUsers = [ {
-          name = cfg.settings.database.user;
-          ensureDBOwnership = true;
-        } ];
-        ensureDatabases = [ cfg.settings.database.name ];
-      };
-
-      redis.servers.pretix.enable = withRedis;
-    };
-
-    systemd.services = let
-      commonUnitConfig = {
-        environment.PRETIX_CONFIG_FILE = configFile;
-        serviceConfig = {
-          User = "pretix";
-          Group = "pretix";
-          EnvironmentFile = optionals (cfg.environmentFile != null) [
-            cfg.environmentFile
-          ];
-          StateDirectory = [
-            "pretix"
-          ];
-          StateDirectoryMode = "0750";
-          CacheDirectory = "pretix";
-          LogsDirectory = "pretix";
-          WorkingDirectory = cfg.settings.pretix.datadir;
-          SupplementaryGroups = optionals withRedis [
-            "redis-pretix"
-          ];
-          AmbientCapabilities = "";
-          CapabilityBoundingSet = [ "" ];
-          DevicePolicy = "closed";
-          LockPersonality = true;
-          MemoryDenyWriteExecute = false; # required by pdftk
-          NoNewPrivileges = true;
-          PrivateDevices = true;
-          PrivateTmp = true;
-          ProcSubset = "pid";
-          ProtectControlGroups = true;
-          ProtectHome = true;
-          ProtectHostname = true;
-          ProtectKernelLogs = true;
-          ProtectKernelModules = true;
-          ProtectKernelTunables = true;
-          ProtectProc = "invisible";
-          ProtectSystem = "strict";
-          RemoveIPC = true;
-          RestrictAddressFamilies = [
-            "AF_INET"
-            "AF_INET6"
-            "AF_UNIX"
-          ];
-          RestrictNamespaces = true;
-          RestrictRealtime = true;
-          RestrictSUIDSGID = true;
-          SystemCallArchitectures = "native";
-          SystemCallFilter = [
-            "@system-service"
-            "~@privileged"
-            "@chown"
-          ];
-          UMask = "0027";
-        };
-      };
-    in {
-      pretix-web = recursiveUpdate commonUnitConfig {
-        description = "pretix web service";
-        after = [
-          "network.target"
-          "redis-pretix.service"
-          "postgresql.service"
-        ];
-        wantedBy = [ "multi-user.target" ];
-        preStart = ''
-          versionFile="${cfg.settings.pretix.datadir}/.version"
-          version=$(cat "$versionFile" 2>/dev/null || echo 0)
-
-          pluginsFile="${cfg.settings.pretix.datadir}/.plugins"
-          plugins=$(cat "$pluginsFile" 2>/dev/null || echo "")
-          configuredPlugins="${concatMapStringsSep "|" (package: package.name) cfg.plugins}"
-
-          if [[ $version != ${cfg.package.version} || $plugins != $configuredPlugins ]]; then
-            ${getExe' pythonEnv "pretix-manage"} migrate
-
-            echo "${cfg.package.version}" > "$versionFile"
-            echo "$configuredPlugins" > "$pluginsFile"
-          fi
-        '';
-        serviceConfig = {
-          TimeoutStartSec = "5min";
-          ExecStart = "${getExe' pythonEnv "gunicorn"} --bind unix:/run/pretix/pretix.sock ${cfg.gunicorn.extraArgs} pretix.wsgi";
-          RuntimeDirectory = "pretix";
-        };
-      };
-
-      pretix-periodic = recursiveUpdate commonUnitConfig {
-        description = "pretix periodic task runner";
-        # every 15 minutes
-        startAt = [ "*:3,18,33,48" ];
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = "${getExe' pythonEnv "pretix-manage"} runperiodic";
-        };
-      };
-
-      pretix-worker = recursiveUpdate commonUnitConfig {
-        description = "pretix asynchronous job runner";
-        after = [
-          "network.target"
-          "redis-pretix.service"
-          "postgresql.service"
-        ];
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig.ExecStart = "${getExe' pythonEnv "celery"} -A pretix.celery_app worker ${cfg.celery.extraArgs}";
-      };
-
-      nginx.serviceConfig.SupplementaryGroups = mkIf cfg.nginx.enable [ "pretix" ];
-    };
-
-    systemd.sockets.pretix-web.socketConfig = {
-      ListenStream = "/run/pretix/pretix.sock";
-      SocketUser = "nginx";
-    };
-
-    users = {
-      groups.${cfg.group} = {};
-      users.${cfg.user} = {
-        isSystemUser = true;
-        inherit (cfg) group;
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/prosody-filer.nix b/nixos/modules/services/web-apps/prosody-filer.nix
deleted file mode 100644
index 91880cab2976..000000000000
--- a/nixos/modules/services/web-apps/prosody-filer.nix
+++ /dev/null
@@ -1,86 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-let
-
-  cfg = config.services.prosody-filer;
-
-  settingsFormat = pkgs.formats.toml { };
-  configFile = settingsFormat.generate "prosody-filer.toml" cfg.settings;
-in {
-
-  options = {
-    services.prosody-filer = {
-      enable = mkEnableOption "Prosody Filer XMPP upload file server";
-
-      settings = mkOption {
-        description = ''
-          Configuration for Prosody Filer.
-          Refer to <https://github.com/ThomasLeister/prosody-filer#configure-prosody-filer> for details on supported values.
-        '';
-
-        type = settingsFormat.type;
-
-        example = {
-          secret = "mysecret";
-          storeDir = "/srv/http/nginx/prosody-upload";
-        };
-
-        defaultText = literalExpression ''
-          {
-            listenport = mkDefault "127.0.0.1:5050";
-            uploadSubDir = mkDefault "upload/";
-          }
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.prosody-filer.settings = {
-      listenport = mkDefault "127.0.0.1:5050";
-      uploadSubDir = mkDefault "upload/";
-    };
-
-    users.users.prosody-filer = {
-      group = "prosody-filer";
-      isSystemUser = true;
-    };
-
-    users.groups.prosody-filer = { };
-
-    systemd.services.prosody-filer = {
-      description = "Prosody file upload server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-
-      serviceConfig = {
-        User = "prosody-filer";
-        Group = "prosody-filer";
-        ExecStart = "${pkgs.prosody-filer}/bin/prosody-filer -config ${configFile}";
-        Restart = "on-failure";
-        CapabilityBoundingSet = "";
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateTmp = true;
-        PrivateMounts = true;
-        ProtectHome = true;
-        ProtectClock = true;
-        ProtectProc = "noaccess";
-        ProcSubset = "pid";
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectControlGroups = true;
-        ProtectHostname = true;
-        RestrictSUIDSGID = true;
-        RestrictRealtime = true;
-        RestrictNamespaces = true;
-        LockPersonality = true;
-        RemoveIPC = true;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        SystemCallFilter = [ "@system-service" "~@privileged" ];
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/rimgo.nix b/nixos/modules/services/web-apps/rimgo.nix
deleted file mode 100644
index 4d35473fda31..000000000000
--- a/nixos/modules/services/web-apps/rimgo.nix
+++ /dev/null
@@ -1,107 +0,0 @@
-{
-  config,
-  lib,
-  pkgs,
-  ...
-}:
-let
-  cfg = config.services.rimgo;
-  inherit (lib)
-    mkOption
-    mkEnableOption
-    mkPackageOption
-    mkDefault
-    mkIf
-    types
-    literalExpression
-    optionalString
-    getExe
-    mapAttrs
-  ;
-in
-{
-  options.services.rimgo = {
-    enable = mkEnableOption "rimgo";
-    package = mkPackageOption pkgs "rimgo" { };
-    settings = mkOption {
-      type = types.submodule {
-        freeformType = with types; attrsOf str;
-        options = {
-          PORT = mkOption {
-            type = types.port;
-            default = 3000;
-            example = 69420;
-            description = "The port to use.";
-          };
-          ADDRESS = mkOption {
-            type = types.str;
-            default = "127.0.0.1";
-            example = "1.1.1.1";
-            description = "The address to listen on.";
-          };
-        };
-      };
-      example = literalExpression ''
-        {
-          PORT = 69420;
-          FORCE_WEBP = "1";
-        }
-      '';
-      description = ''
-        Settings for rimgo, see [the official documentation](https://rimgo.codeberg.page/docs/usage/configuration/) for supported options.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.rimgo = {
-      description = "Rimgo";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-      environment = mapAttrs (_: toString) cfg.settings;
-      serviceConfig = {
-        ExecStart = getExe cfg.package;
-        AmbientCapabilities = mkIf (cfg.settings.PORT < 1024) [
-          "CAP_NET_BIND_SERVICE"
-        ];
-        DynamicUser = true;
-        Restart = "on-failure";
-        RestartSec = "5s";
-        CapabilityBoundingSet = [
-          (optionalString (cfg.settings.PORT < 1024) "CAP_NET_BIND_SERVICE")
-        ];
-        DeviceAllow = [ "" ];
-        LockPersonality = true;
-        MemoryDenyWriteExecute = true;
-        PrivateDevices = true;
-        PrivateUsers = cfg.settings.PORT >= 1024;
-        ProcSubset = "pid";
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        RestrictAddressFamilies = [
-          "AF_INET"
-          "AF_INET6"
-        ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [
-          "@system-service"
-          "~@privileged"
-        ];
-        UMask = "0077";
-      };
-    };
-  };
-
-  meta = {
-    maintainers = with lib.maintainers; [ quantenzitrone ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/rss-bridge.nix b/nixos/modules/services/web-apps/rss-bridge.nix
deleted file mode 100644
index b03c7f7e8069..000000000000
--- a/nixos/modules/services/web-apps/rss-bridge.nix
+++ /dev/null
@@ -1,150 +0,0 @@
-{ config, lib, pkgs, ... }:
-with lib;
-let
-  cfg = config.services.rss-bridge;
-
-  poolName = "rss-bridge";
-
-  configAttr = lib.recursiveUpdate { FileCache.path = "${cfg.dataDir}/cache/"; } cfg.config;
-  cfgHalf = lib.mapAttrsRecursive (path: value: let
-    envName = lib.toUpper ("RSSBRIDGE_" + lib.concatStringsSep "_" path);
-    envValue = if lib.isList value then
-      lib.concatStringsSep "," value
-    else if lib.isBool value then
-      lib.boolToString value
-    else
-      toString value;
-  in "fastcgi_param \"${envName}\" \"${envValue}\";") configAttr;
-  cfgEnv = lib.concatStringsSep "\n" (lib.collect lib.isString cfgHalf);
-in
-{
-  imports = [
-    (mkRenamedOptionModule [ "services" "rss-bridge" "whitelist" ] [ "services" "rss-bridge" "config" "system" "enabled_bridges" ])
-  ];
-
-  options = {
-    services.rss-bridge = {
-      enable = mkEnableOption "rss-bridge";
-
-      user = mkOption {
-        type = types.str;
-        default = "nginx";
-        description = ''
-          User account under which both the service and the web-application run.
-        '';
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "nginx";
-        description = ''
-          Group under which the web-application run.
-        '';
-      };
-
-      pool = mkOption {
-        type = types.str;
-        default = poolName;
-        description = ''
-          Name of existing phpfpm pool that is used to run web-application.
-          If not specified a pool will be created automatically with
-          default values.
-        '';
-      };
-
-      dataDir = mkOption {
-        type = types.str;
-        default = "/var/lib/rss-bridge";
-        description = ''
-          Location in which cache directory will be created.
-          You can put `config.ini.php` in here.
-        '';
-      };
-
-      virtualHost = mkOption {
-        type = types.nullOr types.str;
-        default = "rss-bridge";
-        description = ''
-          Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
-        '';
-      };
-
-      config = mkOption {
-        type = with types; attrsOf (attrsOf (oneOf [ bool int str (listOf str) ]));
-        default = {};
-        defaultText = options.literalExpression "FileCache.path = \"\${config.services.rss-bridge.dataDir}/cache/\"";
-        example = options.literalExpression ''
-          {
-            system.enabled_bridges = [ "*" ];
-            error = {
-              output = "http";
-              report_limit = 5;
-            };
-            FileCache = {
-              enable_purge = true;
-            };
-          }
-        '';
-        description = ''
-          Attribute set of arbitrary config options.
-          Please consult the documentation at the [wiki](https://rss-bridge.github.io/rss-bridge/For_Hosts/Custom_Configuration.html)
-          and [sample config](https://github.com/RSS-Bridge/rss-bridge/blob/master/config.default.ini.php) to see a list of available options.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.phpfpm.pools = mkIf (cfg.pool == poolName) {
-      ${poolName} = {
-        user = cfg.user;
-        settings = mapAttrs (name: mkDefault) {
-          "listen.owner" = cfg.user;
-          "listen.group" = cfg.user;
-          "listen.mode" = "0600";
-          "pm" = "dynamic";
-          "pm.max_children" = 75;
-          "pm.start_servers" = 10;
-          "pm.min_spare_servers" = 5;
-          "pm.max_spare_servers" = 20;
-          "pm.max_requests" = 500;
-          "catch_workers_output" = 1;
-        };
-      };
-    };
-    systemd.tmpfiles.settings.rss-bridge = let
-      perm = {
-        mode = "0750";
-        user = cfg.user;
-        group = cfg.group;
-      };
-    in {
-      "${configAttr.FileCache.path}".d = perm;
-      "${cfg.dataDir}/config.ini.php".z = perm;
-    };
-
-    services.nginx = mkIf (cfg.virtualHost != null) {
-      enable = true;
-      virtualHosts = {
-        ${cfg.virtualHost} = {
-          root = "${pkgs.rss-bridge}";
-
-          locations."/" = {
-            tryFiles = "$uri /index.php$is_args$args";
-          };
-
-          locations."~ ^/index.php(/|$)" = {
-            extraConfig = ''
-              include ${config.services.nginx.package}/conf/fastcgi_params;
-              fastcgi_split_path_info ^(.+\.php)(/.+)$;
-              fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket};
-              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-              fastcgi_param RSSBRIDGE_DATA ${cfg.dataDir};
-              ${cfgEnv}
-            '';
-          };
-        };
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/selfoss.nix b/nixos/modules/services/web-apps/selfoss.nix
deleted file mode 100644
index 899976ac696c..000000000000
--- a/nixos/modules/services/web-apps/selfoss.nix
+++ /dev/null
@@ -1,164 +0,0 @@
-{ config, lib, pkgs, ... }:
-with lib;
-let
-  cfg = config.services.selfoss;
-
-  poolName = "selfoss_pool";
-
-  dataDir = "/var/lib/selfoss";
-
-  selfoss-config =
-  let
-    db_type = cfg.database.type;
-    default_port = if (db_type == "mysql") then 3306 else 5342;
-  in
-  pkgs.writeText "selfoss-config.ini" ''
-    [globals]
-    ${lib.optionalString (db_type != "sqlite") ''
-      db_type=${db_type}
-      db_host=${cfg.database.host}
-      db_database=${cfg.database.name}
-      db_username=${cfg.database.user}
-      db_password=${cfg.database.password}
-      db_port=${toString (if (cfg.database.port != null) then cfg.database.port
-                    else default_port)}
-    ''
-    }
-    ${cfg.extraConfig}
-  '';
-in
-  {
-    options = {
-      services.selfoss = {
-        enable = mkEnableOption "selfoss";
-
-        user = mkOption {
-          type = types.str;
-          default = "nginx";
-          description = ''
-            User account under which both the service and the web-application run.
-          '';
-        };
-
-        pool = mkOption {
-          type = types.str;
-          default = "${poolName}";
-          description = ''
-            Name of existing phpfpm pool that is used to run web-application.
-            If not specified a pool will be created automatically with
-            default values.
-          '';
-        };
-
-      database = {
-        type = mkOption {
-          type = types.enum ["pgsql" "mysql" "sqlite"];
-          default = "sqlite";
-          description = ''
-            Database to store feeds. Supported are sqlite, pgsql and mysql.
-          '';
-        };
-
-        host = mkOption {
-          type = types.str;
-          default = "localhost";
-          description = ''
-            Host of the database (has no effect if type is "sqlite").
-          '';
-        };
-
-        name = mkOption {
-          type = types.str;
-          default = "tt_rss";
-          description = ''
-            Name of the existing database (has no effect if type is "sqlite").
-          '';
-        };
-
-        user = mkOption {
-          type = types.str;
-          default = "tt_rss";
-          description = ''
-            The database user. The user must exist and has access to
-            the specified database (has no effect if type is "sqlite").
-          '';
-        };
-
-        password = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          description = ''
-            The database user's password (has no effect if type is "sqlite").
-          '';
-        };
-
-        port = mkOption {
-          type = types.nullOr types.int;
-          default = null;
-          description = ''
-            The database's port. If not set, the default ports will be
-            provided (5432 and 3306 for pgsql and mysql respectively)
-            (has no effect if type is "sqlite").
-          '';
-        };
-      };
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = ''
-          Extra configuration added to config.ini
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
-      ${poolName} = {
-        user = "nginx";
-        settings = mapAttrs (name: mkDefault) {
-          "listen.owner" = "nginx";
-          "listen.group" = "nginx";
-          "listen.mode" = "0600";
-          "pm" = "dynamic";
-          "pm.max_children" = 75;
-          "pm.start_servers" = 10;
-          "pm.min_spare_servers" = 5;
-          "pm.max_spare_servers" = 20;
-          "pm.max_requests" = 500;
-          "catch_workers_output" = 1;
-        };
-      };
-    };
-
-    systemd.services.selfoss-config = {
-      serviceConfig.Type = "oneshot";
-      script = ''
-        mkdir -m 755 -p ${dataDir}
-        cd ${dataDir}
-
-        # Delete all but the "data" folder
-        ls | grep -v data | while read line; do rm -rf $line; done || true
-
-        # Create the files
-        cp -r "${pkgs.selfoss}/"* "${dataDir}"
-        ln -sf "${selfoss-config}" "${dataDir}/config.ini"
-        chown -R "${cfg.user}" "${dataDir}"
-        chmod -R 755 "${dataDir}"
-      '';
-      wantedBy = [ "multi-user.target" ];
-    };
-
-    systemd.services.selfoss-update = {
-      serviceConfig = {
-        ExecStart = "${pkgs.php}/bin/php ${dataDir}/cliupdate.php";
-        User = "${cfg.user}";
-      };
-      startAt = "hourly";
-      after = [ "selfoss-config.service" ];
-      wantedBy = [ "multi-user.target" ];
-
-    };
-
-  };
-}
diff --git a/nixos/modules/services/web-apps/sftpgo.nix b/nixos/modules/services/web-apps/sftpgo.nix
deleted file mode 100644
index 3ad6d0436afc..000000000000
--- a/nixos/modules/services/web-apps/sftpgo.nix
+++ /dev/null
@@ -1,368 +0,0 @@
-{ options, config, lib, pkgs, utils, ... }:
-
-with lib;
-
-let
-  cfg = config.services.sftpgo;
-  defaultUser = "sftpgo";
-  settingsFormat = pkgs.formats.json {};
-  configFile = settingsFormat.generate "sftpgo.json" cfg.settings;
-  hasPrivilegedPorts = any (port: port > 0 && port < 1024) (
-    catAttrs "port" (cfg.settings.httpd.bindings
-      ++ cfg.settings.ftpd.bindings
-      ++ cfg.settings.sftpd.bindings
-      ++ cfg.settings.webdavd.bindings
-    )
-  );
-in
-{
-  options.services.sftpgo = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = "sftpgo";
-    };
-
-    package = mkPackageOption pkgs "sftpgo" { };
-
-    extraArgs = mkOption {
-      type = with types; listOf str;
-      default = [];
-      description = ''
-        Additional command line arguments to pass to the sftpgo daemon.
-      '';
-      example = [ "--log-level" "info" ];
-    };
-
-    dataDir = mkOption {
-      type = types.str;
-      default = "/var/lib/sftpgo";
-      description = ''
-        The directory where SFTPGo stores its data files.
-      '';
-    };
-
-    user = mkOption {
-      type = types.str;
-      default = defaultUser;
-      description = ''
-        User account name under which SFTPGo runs.
-      '';
-    };
-
-    group = mkOption {
-      type = types.str;
-      default = defaultUser;
-      description = ''
-        Group name under which SFTPGo runs.
-      '';
-    };
-
-    loadDataFile = mkOption {
-      default = null;
-      type = with types; nullOr path;
-      description = ''
-        Path to a json file containing users and folders to load (or update) on startup.
-        Check the [documentation](https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md)
-        for the `--loaddata-from` command line argument for more info.
-      '';
-    };
-
-    settings = mkOption {
-      default = {};
-      description = ''
-        The primary sftpgo configuration. See the
-        [configuration reference](https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md)
-        for possible values.
-      '';
-      type = with types; submodule {
-        freeformType = settingsFormat.type;
-        options = {
-          httpd.bindings = mkOption {
-            default = [];
-            description = ''
-              Configure listen addresses and ports for httpd.
-            '';
-            type = types.listOf (types.submodule {
-              freeformType = settingsFormat.type;
-              options = {
-                address = mkOption {
-                  type = types.str;
-                  default = "127.0.0.1";
-                  description = ''
-                    Network listen address. Leave blank to listen on all available network interfaces.
-                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
-                  '';
-                };
-
-                port = mkOption {
-                  type = types.port;
-                  default = 8080;
-                  description = ''
-                    The port for serving HTTP(S) requests.
-
-                    Setting the port to `0` disables listening on this interface binding.
-                  '';
-                };
-
-                enable_web_admin = mkOption {
-                  type = types.bool;
-                  default = true;
-                  description = ''
-                    Enable the built-in web admin for this interface binding.
-                  '';
-                };
-
-                enable_web_client = mkOption {
-                  type = types.bool;
-                  default = true;
-                  description = ''
-                    Enable the built-in web client for this interface binding.
-                  '';
-                };
-              };
-            });
-          };
-
-          ftpd.bindings = mkOption {
-            default = [];
-            description = ''
-              Configure listen addresses and ports for ftpd.
-            '';
-            type = types.listOf (types.submodule {
-              freeformType = settingsFormat.type;
-              options = {
-                address = mkOption {
-                  type = types.str;
-                  default = "127.0.0.1";
-                  description = ''
-                    Network listen address. Leave blank to listen on all available network interfaces.
-                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
-                  '';
-                };
-
-                port = mkOption {
-                  type = types.port;
-                  default = 0;
-                  description = ''
-                    The port for serving FTP requests.
-
-                    Setting the port to `0` disables listening on this interface binding.
-                  '';
-                };
-              };
-            });
-          };
-
-          sftpd.bindings = mkOption {
-            default = [];
-            description = ''
-              Configure listen addresses and ports for sftpd.
-            '';
-            type = types.listOf (types.submodule {
-              freeformType = settingsFormat.type;
-              options = {
-                address = mkOption {
-                  type = types.str;
-                  default = "127.0.0.1";
-                  description = ''
-                    Network listen address. Leave blank to listen on all available network interfaces.
-                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
-                  '';
-                };
-
-                port = mkOption {
-                  type = types.port;
-                  default = 0;
-                  description = ''
-                    The port for serving SFTP requests.
-
-                    Setting the port to `0` disables listening on this interface binding.
-                  '';
-                };
-              };
-            });
-          };
-
-          webdavd.bindings = mkOption {
-            default = [];
-            description = ''
-              Configure listen addresses and ports for webdavd.
-            '';
-            type = types.listOf (types.submodule {
-              freeformType = settingsFormat.type;
-              options = {
-                address = mkOption {
-                  type = types.str;
-                  default = "127.0.0.1";
-                  description = ''
-                    Network listen address. Leave blank to listen on all available network interfaces.
-                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
-                  '';
-                };
-
-                port = mkOption {
-                  type = types.port;
-                  default = 0;
-                  description = ''
-                    The port for serving WebDAV requests.
-
-                    Setting the port to `0` disables listening on this interface binding.
-                  '';
-                };
-              };
-            });
-          };
-
-          smtp = mkOption {
-            default = {};
-            description = ''
-              SMTP configuration section.
-            '';
-            type = types.submodule {
-              freeformType = settingsFormat.type;
-              options = {
-                host = mkOption {
-                  type = types.str;
-                  default = "";
-                  description = ''
-                    Location of SMTP email server. Leave empty to disable email sending capabilities.
-                  '';
-                };
-
-                port = mkOption {
-                  type = types.port;
-                  default = 465;
-                  description = "Port of the SMTP Server.";
-                };
-
-                encryption = mkOption {
-                  type = types.enum [ 0 1 2 ];
-                  default = 1;
-                  description = ''
-                    Encryption scheme:
-                    - `0`: No encryption
-                    - `1`: TLS
-                    - `2`: STARTTLS
-                  '';
-                };
-
-                auth_type = mkOption {
-                  type = types.enum [ 0 1 2 ];
-                  default = 0;
-                  description = ''
-                    - `0`: Plain
-                    - `1`: Login
-                    - `2`: CRAM-MD5
-                  '';
-                };
-
-                user = mkOption {
-                  type = types.str;
-                  default = "sftpgo";
-                  description = "SMTP username.";
-                };
-
-                from = mkOption {
-                  type = types.str;
-                  default = "SFTPGo <sftpgo@example.com>";
-                  description = ''
-                    From address.
-                  '';
-                };
-              };
-            };
-          };
-        };
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.sftpgo.settings = (mapAttrs (name: mkDefault) {
-      ftpd.bindings = [{ port = 0; }];
-      httpd.bindings = [{ port = 0; }];
-      sftpd.bindings = [{ port = 0; }];
-      webdavd.bindings = [{ port = 0; }];
-      httpd.openapi_path = "${cfg.package}/share/sftpgo/openapi";
-      httpd.templates_path = "${cfg.package}/share/sftpgo/templates";
-      httpd.static_files_path = "${cfg.package}/share/sftpgo/static";
-      smtp.templates_path = "${cfg.package}/share/sftpgo/templates";
-    });
-
-    users = optionalAttrs (cfg.user == defaultUser) {
-      users = {
-        ${defaultUser} = {
-          description = "SFTPGo system user";
-          isSystemUser = true;
-          group = defaultUser;
-          home = cfg.dataDir;
-        };
-      };
-
-      groups = {
-        ${defaultUser} = {
-          members = [ defaultUser ];
-        };
-      };
-    };
-
-    systemd.services.sftpgo = {
-      description = "SFTPGo daemon";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      environment = {
-        SFTPGO_CONFIG_FILE = mkDefault configFile;
-        SFTPGO_LOG_FILE_PATH = mkDefault ""; # log to journal
-        SFTPGO_LOADDATA_FROM = mkIf (cfg.loadDataFile != null) cfg.loadDataFile;
-      };
-
-      serviceConfig = mkMerge [
-        ({
-          Type = "simple";
-          User = cfg.user;
-          Group = cfg.group;
-          WorkingDirectory = cfg.dataDir;
-          ReadWritePaths = [ cfg.dataDir ];
-          LimitNOFILE = 8192; # taken from upstream
-          KillMode = "mixed";
-          ExecStart = "${cfg.package}/bin/sftpgo serve ${utils.escapeSystemdExecArgs cfg.extraArgs}";
-          ExecReload = "${pkgs.util-linux}/bin/kill -s HUP $MAINPID";
-
-          # Service hardening
-          CapabilityBoundingSet = [ (optionalString hasPrivilegedPorts "CAP_NET_BIND_SERVICE") ];
-          DevicePolicy = "closed";
-          LockPersonality = true;
-          NoNewPrivileges = true;
-          PrivateDevices = true;
-          PrivateTmp = true;
-          ProcSubset = "pid";
-          ProtectClock = true;
-          ProtectControlGroups = true;
-          ProtectHome = true;
-          ProtectHostname = true;
-          ProtectKernelLogs = true;
-          ProtectKernelModules = true;
-          ProtectKernelTunables = true;
-          ProtectProc = "invisible";
-          ProtectSystem = "strict";
-          RemoveIPC = true;
-          RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
-          RestrictNamespaces = true;
-          RestrictRealtime = true;
-          RestrictSUIDSGID = true;
-          SystemCallArchitectures = "native";
-          SystemCallFilter = [ "@system-service" "~@privileged" ];
-          UMask = "0077";
-        })
-        (mkIf hasPrivilegedPorts {
-          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
-        })
-        (mkIf (cfg.dataDir == options.services.sftpgo.dataDir.default) {
-          StateDirectory = baseNameOf cfg.dataDir;
-        })
-      ];
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/shiori.nix b/nixos/modules/services/web-apps/shiori.nix
deleted file mode 100644
index 022bb5e43881..000000000000
--- a/nixos/modules/services/web-apps/shiori.nix
+++ /dev/null
@@ -1,98 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.shiori;
-in {
-  options = {
-    services.shiori = {
-      enable = mkEnableOption "Shiori simple bookmarks manager";
-
-      package = mkPackageOption pkgs "shiori" { };
-
-      address = mkOption {
-        type = types.str;
-        default = "";
-        description = ''
-          The IP address on which Shiori will listen.
-          If empty, listens on all interfaces.
-        '';
-      };
-
-      port = mkOption {
-        type = types.port;
-        default = 8080;
-        description = "The port of the Shiori web application";
-      };
-
-      webRoot = mkOption {
-        type = types.str;
-        default = "/";
-        example = "/shiori";
-        description = "The root of the Shiori web application";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.shiori = with cfg; {
-      description = "Shiori simple bookmarks manager";
-      wantedBy = [ "multi-user.target" ];
-
-      environment.SHIORI_DIR = "/var/lib/shiori";
-
-      serviceConfig = {
-        ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}' --webroot '${webRoot}'";
-
-        DynamicUser = true;
-        StateDirectory = "shiori";
-        # As the RootDirectory
-        RuntimeDirectory = "shiori";
-
-        # Security options
-
-        BindReadOnlyPaths = [
-          "/nix/store"
-
-          # For SSL certificates, and the resolv.conf
-          "/etc"
-        ];
-
-        CapabilityBoundingSet = "";
-
-        DeviceAllow = "";
-
-        LockPersonality = true;
-
-        MemoryDenyWriteExecute = true;
-
-        PrivateDevices = true;
-        PrivateUsers = true;
-
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-
-        RestrictNamespaces = true;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-
-        RootDirectory = "/run/shiori";
-
-        SystemCallArchitectures = "native";
-        SystemCallErrorNumber = "EPERM";
-        SystemCallFilter = [
-          "@system-service"
-          "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"
-        ];
-      };
-    };
-  };
-
-  meta.maintainers = with maintainers; [ minijackson ];
-}
diff --git a/nixos/modules/services/web-apps/silverbullet.nix b/nixos/modules/services/web-apps/silverbullet.nix
deleted file mode 100644
index c316d074cbaa..000000000000
--- a/nixos/modules/services/web-apps/silverbullet.nix
+++ /dev/null
@@ -1,123 +0,0 @@
-{ config
-, pkgs
-, lib
-, ...
-}:
-let
-  cfg = config.services.silverbullet;
-  defaultUser = "silverbullet";
-  defaultGroup = defaultUser;
-  defaultSpaceDir = "/var/lib/silverbullet";
-in
-{
-  options = {
-    services.silverbullet = {
-      enable = lib.mkEnableOption "Silverbullet, an open-source, self-hosted, offline-capable Personal Knowledge Management (PKM) web application.";
-
-      package = lib.mkPackageOptionMD pkgs "silverbullet" { };
-
-      openFirewall = lib.mkOption {
-        type = lib.types.bool;
-        default = false;
-        description = "Open port in the firewall.";
-      };
-
-      listenPort = lib.mkOption {
-        type = lib.types.int;
-        default = 3000;
-        description = "Port to listen on.";
-      };
-
-      listenAddress = lib.mkOption {
-        type = lib.types.str;
-        default = "127.0.0.1";
-        description = "Address or hostname to listen on. Defaults to 127.0.0.1.";
-      };
-
-      spaceDir = lib.mkOption {
-        type = lib.types.path;
-        default = defaultSpaceDir;
-        example = "/home/yourUser/silverbullet";
-        description = ''
-          Folder to store Silverbullet's space/workspace.
-          By default it is located at `${defaultSpaceDir}`.
-        '';
-      };
-
-      user = lib.mkOption {
-        type = lib.types.str;
-        default = defaultUser;
-        example = "yourUser";
-        description = ''
-          The user to run Silverbullet as.
-          By default, a user named `${defaultUser}` will be created whose space
-          directory is [spaceDir](#opt-services.silverbullet.spaceDir).
-        '';
-      };
-
-      group = lib.mkOption {
-        type = lib.types.str;
-        default = defaultGroup;
-        example = "yourGroup";
-        description = ''
-          The group to run Silverbullet under.
-          By default, a group named `${defaultGroup}` will be created.
-        '';
-      };
-
-      envFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/etc/silverbullet.env";
-        description = ''
-          File containing extra environment variables. For example:
-
-          ```
-          SB_USER=user:password
-          SB_AUTH_TOKEN=abcdefg12345
-          ```
-        '';
-      };
-
-      extraArgs = lib.mkOption {
-        type = lib.types.listOf lib.types.str;
-        default = [ ];
-        example = [ "--db /path/to/silverbullet.db" ];
-        description = "Extra arguments passed to silverbullet.";
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.silverbullet = {
-      description = "Silverbullet service";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      preStart = lib.mkIf (!lib.hasPrefix "/var/lib/" cfg.spaceDir) "mkdir -p '${cfg.spaceDir}'";
-      serviceConfig = {
-        Type = "simple";
-        User = "${cfg.user}";
-        Group = "${cfg.group}";
-        EnvironmentFile = lib.mkIf (cfg.envFile != null) "${cfg.envFile}";
-        StateDirectory = lib.mkIf (lib.hasPrefix "/var/lib/" cfg.spaceDir) (lib.last (lib.splitString "/" cfg.spaceDir));
-        ExecStart = "${lib.getExe cfg.package} --port ${toString cfg.listenPort} --hostname '${cfg.listenAddress}' '${cfg.spaceDir}' " + lib.concatStringsSep " " cfg.extraArgs;
-        Restart = "on-failure";
-      };
-    };
-
-    networking.firewall = lib.mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.listenPort ];
-    };
-
-    users.users.${defaultUser} = lib.mkIf (cfg.user == defaultUser) {
-      isSystemUser = true;
-      group = cfg.group;
-      description = "Silverbullet daemon user";
-    };
-
-    users.groups.${defaultGroup} = lib.mkIf (cfg.group == defaultGroup) { };
-  };
-
-  meta.maintainers = with lib.maintainers; [ aorith ];
-}
diff --git a/nixos/modules/services/web-apps/simplesamlphp.nix b/nixos/modules/services/web-apps/simplesamlphp.nix
deleted file mode 100644
index e970266fc17d..000000000000
--- a/nixos/modules/services/web-apps/simplesamlphp.nix
+++ /dev/null
@@ -1,128 +0,0 @@
-{
-  config,
-  lib,
-  pkgs,
-  ...
-}:
-let
-  cfg = config.services.simplesamlphp;
-
-  format = pkgs.formats.php { finalVariable = "config"; };
-
-  generateConfig =
-    opts:
-    pkgs.runCommand "simplesamlphp-config" { } ''
-      mkdir $out
-      cp ${format.generate "config.php" opts.settings} $out/config.php
-      cp ${format.generate "authsources.php" opts.authSources} $out/authsources.php
-    '';
-in
-{
-  meta = {
-    maintainers = with lib.maintainers; [ nhnn ];
-  };
-
-  options.services.simplesamlphp =
-    with lib;
-    mkOption {
-      type = types.attrsOf (
-        types.submodule (
-          { config, ... }:
-          {
-            options = {
-              package = mkPackageOption pkgs "simplesamlphp" { };
-              configureNginx = mkOption {
-                type = types.bool;
-                default = true;
-                description = "Configure nginx as a reverse proxy for SimpleSAMLphp.";
-              };
-              phpfpmPool = mkOption {
-                type = types.str;
-                description = "The PHP-FPM pool that serves SimpleSAMLphp instance.";
-              };
-              localDomain = mkOption {
-                type = types.str;
-                description = "The domain serving your SimpleSAMLphp instance. This option modifies only /saml route.";
-              };
-              settings = mkOption {
-                type = types.submodule {
-                  freeformType = format.type;
-                  options = {
-                    baseurlpath = mkOption {
-                      type = types.str;
-                      example = "https://filesender.example.com/saml/";
-                      description = "URL where SimpleSAMLphp can be reached.";
-                    };
-                  };
-                };
-                default = { };
-                description = ''
-                  Configuration options used by SimpleSAMLphp.
-                  See [](https://simplesamlphp.org/docs/stable/simplesamlphp-install)
-                  for available options.
-                '';
-              };
-
-              authSources = mkOption {
-                type = format.type;
-                default = { };
-                description = ''
-                  Auth sources options used by SimpleSAMLphp.
-                '';
-              };
-
-              libDir = mkOption {
-                type = types.str;
-                readOnly = true;
-                description = ''
-                  Path to the SimpleSAMLphp library directory.
-                '';
-              };
-              configDir = mkOption {
-                type = types.str;
-                readOnly = true;
-                description = ''
-                  Path to the SimpleSAMLphp config directory.
-                '';
-              };
-            };
-            config = {
-              libDir = "${config.package}/share/php/simplesamlphp/";
-              configDir = "${generateConfig config}";
-            };
-          }
-        )
-      );
-      default = { };
-      description = "Instances of SimpleSAMLphp. This module is designed to work with already existing PHP-FPM pool and NGINX virtualHost.";
-    };
-
-  config = {
-    services.phpfpm.pools = lib.mapAttrs' (
-      phpfpmName: opts:
-      lib.nameValuePair opts.phpfpmPool { phpEnv.SIMPLESAMLPHP_CONFIG_DIR = "${generateConfig opts}"; }
-    ) cfg;
-
-    services.nginx.virtualHosts = lib.mapAttrs' (
-      phpfpmName: opts:
-      lib.nameValuePair opts.localDomain (
-        lib.mkIf opts.configureNginx {
-          locations."^~ /saml/" = {
-            alias = "${opts.package}/share/php/simplesamlphp/www/";
-            extraConfig = ''
-                location ~ ^(?<prefix>/saml)(?<phpfile>.+?\.php)(?<pathinfo>/.*)?$ {
-                  include ${pkgs.nginx}/conf/fastcgi.conf;
-                  fastcgi_split_path_info  ^(.+\.php)(/.+)$;
-                  fastcgi_pass  unix:${config.services.phpfpm.pools.${phpfpmName}.socket};
-                  fastcgi_intercept_errors on;
-                  fastcgi_param SCRIPT_FILENAME $document_root$phpfile;
-                  fastcgi_param SCRIPT_NAME /saml$phpfile;
-                  fastcgi_param PATH_INFO $pathinfo if_not_empty;
-              }
-            '';
-          };
-        }
-      )
-    ) cfg;
-  };
-}
diff --git a/nixos/modules/services/web-apps/slskd.nix b/nixos/modules/services/web-apps/slskd.nix
deleted file mode 100644
index 6254fe294eee..000000000000
--- a/nixos/modules/services/web-apps/slskd.nix
+++ /dev/null
@@ -1,333 +0,0 @@
-{ lib, pkgs, config, ... }:
-
-let
-  settingsFormat = pkgs.formats.yaml {};
-  defaultUser = "slskd";
-in {
-  options.services.slskd = with lib; with types; {
-    enable = mkEnableOption "slskd";
-
-    package = mkPackageOptionMD pkgs "slskd" { };
-
-    user = mkOption {
-      type = types.str;
-      default = defaultUser;
-      description = "User account under which slskd runs.";
-    };
-
-    group = mkOption {
-      type = types.str;
-      default = defaultUser;
-      description = "Group under which slskd runs.";
-    };
-
-    domain = mkOption {
-      type = types.nullOr types.str;
-      description = ''
-        If non-null, enables an nginx reverse proxy virtual host at this FQDN,
-        at the path configurated with `services.slskd.web.url_base`.
-      '';
-      example = "slskd.example.com";
-    };
-
-    nginx = mkOption {
-      type = types.submodule (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
-      default = {};
-      example = lib.literalExpression ''
-        {
-          enableACME = true;
-          forceHttps = true;
-        }
-      '';
-      description = ''
-        This option customizes the nginx virtual host set up for slskd.
-      '';
-    };
-
-    environmentFile = mkOption {
-      type = path;
-      description = ''
-        Path to the environment file sourced on startup.
-        It must at least contain the variables `SLSKD_SLSK_USERNAME` and `SLSKD_SLSK_PASSWORD`.
-        Web interface credentials should also be set here in `SLSKD_USERNAME` and `SLSKD_PASSWORD`.
-        Other, optional credentials like SOCKS5 with `SLSKD_SLSK_PROXY_USERNAME` and `SLSKD_SLSK_PROXY_PASSWORD`
-        should all reside here instead of in the world-readable nix store.
-        Variables are documented at https://github.com/slskd/slskd/blob/master/docs/config.md
-      '';
-    };
-
-    openFirewall = mkOption {
-      type = bool;
-      description = "Whether to open the firewall for the soulseek network listen port (not the web interface port).";
-      default = false;
-    };
-
-    settings = mkOption {
-      description = ''
-        Application configuration for slskd. See
-        [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md).
-      '';
-      default = {};
-      type = submodule {
-        freeformType = settingsFormat.type;
-        options = {
-          remote_file_management = mkEnableOption "modification of share contents through the web ui";
-
-          flags = {
-            force_share_scan = mkOption {
-              type = bool;
-              description = "Force a rescan of shares on every startup.";
-            };
-            no_version_check = mkOption {
-              type = bool;
-              default = true;
-              visible = false;
-              description = "Don't perform a version check on startup.";
-            };
-          };
-
-          directories = {
-            incomplete = mkOption {
-              type = nullOr path;
-              description = "Directory where incomplete downloading files are stored.";
-              defaultText = "/var/lib/slskd/incomplete";
-              default = null;
-            };
-            downloads = mkOption {
-              type = nullOr path;
-              description = "Directory where downloaded files are stored.";
-              defaultText = "/var/lib/slskd/downloads";
-              default = null;
-            };
-          };
-
-          shares = {
-            directories = mkOption {
-              type = listOf str;
-              description = ''
-                Paths to shared directories. See
-                [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md#directories)
-                for advanced usage.
-              '';
-              example = lib.literalExpression ''[ "/home/John/Music" "!/home/John/Music/Recordings" "[Music Drive]/mnt" ]'';
-            };
-            filters = mkOption {
-              type = listOf str;
-              example = lib.literalExpression ''[ "\.ini$" "Thumbs.db$" "\.DS_Store$" ]'';
-              description = "Regular expressions of files to exclude from sharing.";
-            };
-          };
-
-          rooms = mkOption {
-            type = listOf str;
-            description = "Chat rooms to join on startup.";
-          };
-
-          soulseek = {
-            description = mkOption {
-              type = str;
-              description = "The user description for the Soulseek network.";
-              defaultText = "A slskd user. https://github.com/slskd/slskd";
-            };
-            listen_port = mkOption {
-              type = port;
-              description = "The port on which to listen for incoming connections.";
-              default = 50300;
-            };
-          };
-
-          global = {
-            # TODO speed units
-            upload = {
-              slots = mkOption {
-                type = ints.unsigned;
-                description = "Limit of the number of concurrent upload slots.";
-              };
-              speed_limit = mkOption {
-                type = ints.unsigned;
-                description = "Total upload speed limit.";
-              };
-            };
-            download = {
-              slots = mkOption {
-                type = ints.unsigned;
-                description = "Limit of the number of concurrent download slots.";
-              };
-              speed_limit = mkOption {
-                type = ints.unsigned;
-                description = "Total upload download limit";
-              };
-            };
-          };
-
-          filters.search.request = mkOption {
-            type = listOf str;
-            example = lib.literalExpression ''[ "^.{1,2}$" ]'';
-            description = "Incoming search requests which match this filter are ignored.";
-          };
-
-          web = {
-            port = mkOption {
-              type = port;
-              default = 5030;
-              description = "The HTTP listen port.";
-            };
-            url_base = mkOption {
-              type = path;
-              default = "/";
-              description = "The base path in the url for web requests.";
-            };
-            # Users should use a reverse proxy instead for https
-            https.disabled = mkOption {
-              type = bool;
-              default = true;
-              description = "Disable the built-in HTTPS server";
-            };
-          };
-
-          retention = {
-            transfers = {
-              upload = {
-                succeeded = mkOption {
-                  type = ints.unsigned;
-                  description = "Lifespan of succeeded upload tasks.";
-                  defaultText = "(indefinite)";
-                };
-                errored = mkOption {
-                  type = ints.unsigned;
-                  description = "Lifespan of errored upload tasks.";
-                  defaultText = "(indefinite)";
-                };
-                cancelled = mkOption {
-                  type = ints.unsigned;
-                  description = "Lifespan of cancelled upload tasks.";
-                  defaultText = "(indefinite)";
-                };
-              };
-              download = {
-                succeeded = mkOption {
-                  type = ints.unsigned;
-                  description = "Lifespan of succeeded download tasks.";
-                  defaultText = "(indefinite)";
-                };
-                errored = mkOption {
-                  type = ints.unsigned;
-                  description = "Lifespan of errored download tasks.";
-                  defaultText = "(indefinite)";
-                };
-                cancelled = mkOption {
-                  type = ints.unsigned;
-                  description = "Lifespan of cancelled download tasks.";
-                  defaultText = "(indefinite)";
-                };
-              };
-            };
-            files = {
-              complete = mkOption {
-                type = ints.unsigned;
-                description = "Lifespan of completely downloaded files in minutes.";
-                example = 20160;
-                defaultText = "(indefinite)";
-              };
-              incomplete = mkOption {
-                type = ints.unsigned;
-                description = "Lifespan of incomplete downloading files in minutes.";
-                defaultText = "(indefinite)";
-              };
-            };
-          };
-
-          logger = {
-            # Disable by default, journald already retains as needed
-            disk = mkOption {
-              type = bool;
-              description = "Whether to log to the application directory.";
-              default = false;
-              visible = false;
-            };
-          };
-        };
-      };
-    };
-  };
-
-  config = let
-    cfg = config.services.slskd;
-
-    confWithoutNullValues = (lib.filterAttrsRecursive (key: value: (builtins.tryEval value).success && value != null) cfg.settings);
-
-    configurationYaml = settingsFormat.generate "slskd.yml" confWithoutNullValues;
-
-  in lib.mkIf cfg.enable {
-
-    # Force off, configuration file is in nix store and is immutable
-    services.slskd.settings.remote_configuration = lib.mkForce false;
-
-    users.users = lib.optionalAttrs (cfg.user == defaultUser) {
-      "${defaultUser}" = {
-        group = cfg.group;
-        isSystemUser = true;
-      };
-    };
-
-    users.groups = lib.optionalAttrs (cfg.group == defaultUser) {
-      "${defaultUser}" = {};
-    };
-
-    systemd.services.slskd = {
-      description = "A modern client-server application for the Soulseek file sharing network";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        Type = "simple";
-        User = cfg.user;
-        Group = cfg.group;
-        EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
-        StateDirectory = "slskd";  # Creates /var/lib/slskd and manages permissions
-        ExecStart = "${cfg.package}/bin/slskd --app-dir /var/lib/slskd --config ${configurationYaml}";
-        Restart = "on-failure";
-        ReadOnlyPaths = map (d: builtins.elemAt (builtins.split "[^/]*(/.+)" d) 1) cfg.settings.shares.directories;
-        ReadWritePaths =
-          (lib.optional (cfg.settings.directories.incomplete != null) cfg.settings.directories.incomplete) ++
-          (lib.optional (cfg.settings.directories.downloads != null) cfg.settings.directories.downloads);
-        LockPersonality = true;
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateMounts = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        ProtectSystem = "strict";
-        RemoveIPC = true;
-        RestrictNamespaces = true;
-        RestrictSUIDSGID = true;
-      };
-    };
-
-    networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.settings.soulseek.listen_port;
-
-    services.nginx = lib.mkIf (cfg.domain != null) {
-      enable = lib.mkDefault true;
-      virtualHosts."${cfg.domain}" = lib.mkMerge [
-        cfg.nginx
-        {
-          locations."${cfg.settings.web.url_base}" = {
-            proxyPass = "http://127.0.0.1:${toString cfg.settings.web.port}";
-            proxyWebsockets = true;
-          };
-        }
-      ];
-    };
-  };
-
-  meta = {
-    maintainers = with lib.maintainers; [ ppom melvyn2 ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/snipe-it.nix b/nixos/modules/services/web-apps/snipe-it.nix
deleted file mode 100644
index 272dd23d7271..000000000000
--- a/nixos/modules/services/web-apps/snipe-it.nix
+++ /dev/null
@@ -1,515 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.snipe-it;
-  snipe-it = pkgs.snipe-it.override {
-    dataDir = cfg.dataDir;
-  };
-  db = cfg.database;
-  mail = cfg.mail;
-
-  user = cfg.user;
-  group = cfg.group;
-
-  tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME;
-
-  inherit (snipe-it.passthru) phpPackage;
-
-  # shell script for local administration
-  artisan = (pkgs.writeScriptBin "snipe-it" ''
-    #! ${pkgs.runtimeShell}
-    cd "${snipe-it}/share/php/snipe-it"
-    sudo=exec
-    if [[ "$USER" != ${user} ]]; then
-      sudo='exec /run/wrappers/bin/sudo -u ${user}'
-    fi
-    $sudo ${phpPackage}/bin/php artisan $*
-  '').overrideAttrs (old: {
-    meta = old.meta // {
-      mainProgram = "snipe-it";
-    };
-  });
-in {
-  options.services.snipe-it = {
-
-    enable = mkEnableOption "snipe-it, a free open source IT asset/license management system";
-
-    user = mkOption {
-      default = "snipeit";
-      description = "User snipe-it runs as.";
-      type = types.str;
-    };
-
-    group = mkOption {
-      default = "snipeit";
-      description = "Group snipe-it runs as.";
-      type = types.str;
-    };
-
-    appKeyFile = mkOption {
-      description = ''
-        A file containing the Laravel APP_KEY - a 32 character long,
-        base64 encoded key used for encryption where needed. Can be
-        generated with `head -c 32 /dev/urandom | base64`.
-      '';
-      example = "/run/keys/snipe-it/appkey";
-      type = types.path;
-    };
-
-    hostName = lib.mkOption {
-      type = lib.types.str;
-      default = config.networking.fqdnOrHostName;
-      defaultText = lib.literalExpression "config.networking.fqdnOrHostName";
-      example = "snipe-it.example.com";
-      description = ''
-        The hostname to serve Snipe-IT on.
-      '';
-    };
-
-    appURL = mkOption {
-      description = ''
-        The root URL that you want to host Snipe-IT on. All URLs in Snipe-IT will be generated using this value.
-        If you change this in the future you may need to run a command to update stored URLs in the database.
-        Command example: `snipe-it snipe-it:update-url https://old.example.com https://new.example.com`
-      '';
-      default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostName}";
-      defaultText = ''
-        http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostName}
-      '';
-      example = "https://example.com";
-      type = types.str;
-    };
-
-    dataDir = mkOption {
-      description = "snipe-it data directory";
-      default = "/var/lib/snipe-it";
-      type = types.path;
-    };
-
-    database = {
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Database host address.";
-      };
-      port = mkOption {
-        type = types.port;
-        default = 3306;
-        description = "Database host port.";
-      };
-      name = mkOption {
-        type = types.str;
-        default = "snipeit";
-        description = "Database name.";
-      };
-      user = mkOption {
-        type = types.str;
-        default = user;
-        defaultText = literalExpression "user";
-        description = "Database username.";
-      };
-      passwordFile = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        example = "/run/keys/snipe-it/dbpassword";
-        description = ''
-          A file containing the password corresponding to
-          {option}`database.user`.
-        '';
-      };
-      createLocally = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Create the database and database user locally.";
-      };
-    };
-
-    mail = {
-      driver = mkOption {
-        type = types.enum [ "smtp" "sendmail" ];
-        default = "smtp";
-        description = "Mail driver to use.";
-      };
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Mail host address.";
-      };
-      port = mkOption {
-        type = types.port;
-        default = 1025;
-        description = "Mail host port.";
-      };
-      encryption = mkOption {
-        type = with types; nullOr (enum [ "tls" "ssl" ]);
-        default = null;
-        description = "SMTP encryption mechanism to use.";
-      };
-      user = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        example = "snipeit";
-        description = "Mail username.";
-      };
-      passwordFile = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        example = "/run/keys/snipe-it/mailpassword";
-        description = ''
-          A file containing the password corresponding to
-          {option}`mail.user`.
-        '';
-      };
-      backupNotificationAddress = mkOption {
-        type = types.str;
-        default = "backup@example.com";
-        description = "Email Address to send Backup Notifications to.";
-      };
-      from = {
-        name = mkOption {
-          type = types.str;
-          default = "Snipe-IT Asset Management";
-          description = "Mail \"from\" name.";
-        };
-        address = mkOption {
-          type = types.str;
-          default = "mail@example.com";
-          description = "Mail \"from\" address.";
-        };
-      };
-      replyTo = {
-        name = mkOption {
-          type = types.str;
-          default = "Snipe-IT Asset Management";
-          description = "Mail \"reply-to\" name.";
-        };
-        address = mkOption {
-          type = types.str;
-          default = "mail@example.com";
-          description = "Mail \"reply-to\" address.";
-        };
-      };
-    };
-
-    maxUploadSize = mkOption {
-      type = types.str;
-      default = "18M";
-      example = "1G";
-      description = "The maximum size for uploads (e.g. images).";
-    };
-
-    poolConfig = mkOption {
-      type = with types; attrsOf (oneOf [ str int bool ]);
-      default = {
-        "pm" = "dynamic";
-        "pm.max_children" = 32;
-        "pm.start_servers" = 2;
-        "pm.min_spare_servers" = 2;
-        "pm.max_spare_servers" = 4;
-        "pm.max_requests" = 500;
-      };
-      description = ''
-        Options for the snipe-it PHP pool. See the documentation on `php-fpm.conf`
-        for details on configuration directives.
-      '';
-    };
-
-    nginx = mkOption {
-      type = types.submodule (
-        recursiveUpdate
-          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
-      );
-      default = {};
-      example = literalExpression ''
-        {
-          serverAliases = [
-            "snipe-it.''${config.networking.domain}"
-          ];
-          # To enable encryption and let let's encrypt take care of certificate
-          forceSSL = true;
-          enableACME = true;
-        }
-      '';
-      description = ''
-        With this option, you can customize the nginx virtualHost settings.
-      '';
-    };
-
-    config = mkOption {
-      type = with types;
-        attrsOf
-          (nullOr
-            (either
-              (oneOf [
-                bool
-                int
-                port
-                path
-                str
-              ])
-              (submodule {
-                options = {
-                  _secret = mkOption {
-                    type = nullOr (oneOf [ str path ]);
-                    description = ''
-                      The path to a file containing the value the
-                      option should be set to in the final
-                      configuration file.
-                    '';
-                  };
-                };
-              })));
-      default = {};
-      example = literalExpression ''
-        {
-          ALLOWED_IFRAME_HOSTS = "https://example.com";
-          WKHTMLTOPDF = "''${pkgs.wkhtmltopdf}/bin/wkhtmltopdf";
-          AUTH_METHOD = "oidc";
-          OIDC_NAME = "MyLogin";
-          OIDC_DISPLAY_NAME_CLAIMS = "name";
-          OIDC_CLIENT_ID = "snipe-it";
-          OIDC_CLIENT_SECRET = {_secret = "/run/keys/oidc_secret"};
-          OIDC_ISSUER = "https://keycloak.example.com/auth/realms/My%20Realm";
-          OIDC_ISSUER_DISCOVER = true;
-        }
-      '';
-      description = ''
-        Snipe-IT configuration options to set in the
-        {file}`.env` file.
-        Refer to <https://snipe-it.readme.io/docs/configuration>
-        for details on supported values.
-
-        Settings containing secret data should be set to an attribute
-        set containing the attribute `_secret` - a
-        string pointing to a file containing the value the option
-        should be set to. See the example to get a better picture of
-        this: in the resulting {file}`.env` file, the
-        `OIDC_CLIENT_SECRET` key will be set to the
-        contents of the {file}`/run/keys/oidc_secret`
-        file.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    assertions = [
-      { assertion = db.createLocally -> db.user == user;
-        message = "services.snipe-it.database.user must be set to ${user} if services.snipe-it.database.createLocally is set true.";
-      }
-      { assertion = db.createLocally -> db.passwordFile == null;
-        message = "services.snipe-it.database.passwordFile cannot be specified if services.snipe-it.database.createLocally is set to true.";
-      }
-    ];
-
-    environment.systemPackages = [ artisan ];
-
-    services.snipe-it.config = {
-      APP_ENV = "production";
-      APP_KEY._secret = cfg.appKeyFile;
-      APP_URL = cfg.appURL;
-      DB_HOST = db.host;
-      DB_PORT = db.port;
-      DB_DATABASE = db.name;
-      DB_USERNAME = db.user;
-      DB_PASSWORD._secret = db.passwordFile;
-      MAIL_DRIVER = mail.driver;
-      MAIL_FROM_NAME = mail.from.name;
-      MAIL_FROM_ADDR = mail.from.address;
-      MAIL_REPLYTO_NAME = mail.from.name;
-      MAIL_REPLYTO_ADDR = mail.from.address;
-      MAIL_BACKUP_NOTIFICATION_ADDRESS = mail.backupNotificationAddress;
-      MAIL_HOST = mail.host;
-      MAIL_PORT = mail.port;
-      MAIL_USERNAME = mail.user;
-      MAIL_ENCRYPTION = mail.encryption;
-      MAIL_PASSWORD._secret = mail.passwordFile;
-      APP_SERVICES_CACHE = "/run/snipe-it/cache/services.php";
-      APP_PACKAGES_CACHE = "/run/snipe-it/cache/packages.php";
-      APP_CONFIG_CACHE = "/run/snipe-it/cache/config.php";
-      APP_ROUTES_CACHE = "/run/snipe-it/cache/routes-v7.php";
-      APP_EVENTS_CACHE = "/run/snipe-it/cache/events.php";
-      SESSION_SECURE_COOKIE = tlsEnabled;
-    };
-
-    services.mysql = mkIf db.createLocally {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = [ db.name ];
-      ensureUsers = [
-        { name = db.user;
-          ensurePermissions = { "${db.name}.*" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    services.phpfpm.pools.snipe-it = {
-      inherit user group phpPackage;
-      phpOptions = ''
-        post_max_size = ${cfg.maxUploadSize}
-        upload_max_filesize = ${cfg.maxUploadSize}
-      '';
-      settings = {
-        "listen.mode" = "0660";
-        "listen.owner" = user;
-        "listen.group" = group;
-      } // cfg.poolConfig;
-    };
-
-    services.nginx = {
-      enable = mkDefault true;
-      virtualHosts."${cfg.hostName}" = mkMerge [ cfg.nginx {
-        root = mkForce "${snipe-it}/share/php/snipe-it/public";
-        extraConfig = optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;";
-        locations = {
-          "/" = {
-            index = "index.php";
-            extraConfig = ''try_files $uri $uri/ /index.php?$query_string;'';
-          };
-          "~ \.php$" = {
-            extraConfig = ''
-              try_files $uri $uri/ /index.php?$query_string;
-              include ${config.services.nginx.package}/conf/fastcgi_params;
-              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-              fastcgi_param REDIRECT_STATUS 200;
-              fastcgi_pass unix:${config.services.phpfpm.pools."snipe-it".socket};
-              ${optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;"}
-            '';
-          };
-          "~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
-            extraConfig = "expires 365d;";
-          };
-        };
-      }];
-    };
-
-    systemd.services.snipe-it-setup = {
-      description = "Preparation tasks for snipe-it";
-      before = [ "phpfpm-snipe-it.service" ];
-      after = optional db.createLocally "mysql.service";
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = true;
-        User = user;
-        WorkingDirectory = snipe-it;
-        RuntimeDirectory = "snipe-it/cache";
-        RuntimeDirectoryMode = "0700";
-      };
-      path = [ pkgs.replace-secret artisan ];
-      script =
-        let
-          isSecret  = v: isAttrs v && v ? _secret && (isString v._secret || builtins.isPath v._secret);
-          snipeITEnvVars = lib.generators.toKeyValue {
-            mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
-              mkValueString = v: with builtins;
-                if isInt             v then toString v
-                else if isString     v then "\"${v}\""
-                else if true  ==     v then "true"
-                else if false ==     v then "false"
-                else if isSecret     v then
-                  if (isString v._secret) then
-                    hashString "sha256" v._secret
-                  else
-                    hashString "sha256" (builtins.readFile v._secret)
-                else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
-            };
-          };
-          secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
-          mkSecretReplacement = file: ''
-            replace-secret ${escapeShellArgs [
-              (
-                if (isString file) then
-                  builtins.hashString "sha256" file
-                else
-                  builtins.hashString "sha256" (builtins.readFile file)
-              )
-              file
-              "${cfg.dataDir}/.env"
-            ]}
-          '';
-          secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
-          filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ {} null ])) cfg.config;
-          snipeITEnv = pkgs.writeText "snipeIT.env" (snipeITEnvVars filteredConfig);
-        in ''
-          # error handling
-          set -euo pipefail
-
-          # set permissions
-          umask 077
-
-          # create .env file
-          install -T -m 0600 -o ${user} ${snipeITEnv} "${cfg.dataDir}/.env"
-
-          # replace secrets
-          ${secretReplacements}
-
-          # prepend `base64:` if it does not exist in APP_KEY
-          if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
-              sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
-          fi
-
-          # purge cache
-          rm "${cfg.dataDir}"/bootstrap/cache/*.php || true
-
-          # migrate db
-          ${lib.getExe artisan} migrate --force
-
-          # A placeholder file for invalid barcodes
-          invalid_barcode_location="${cfg.dataDir}/public/uploads/barcodes/invalid_barcode.gif"
-          if [ ! -e "$invalid_barcode_location" ]; then
-              cp ${snipe-it}/share/snipe-it/invalid_barcode.gif "$invalid_barcode_location"
-          fi
-        '';
-    };
-
-    systemd.tmpfiles.rules = [
-      "d ${cfg.dataDir}                              0710 ${user} ${group} - -"
-      "d ${cfg.dataDir}/bootstrap                    0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/bootstrap/cache              0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public                       0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads               0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/accessories   0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/assets        0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/avatars       0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/barcodes      0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/categories    0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/companies     0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/components    0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/consumables   0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/departments   0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/locations     0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/manufacturers 0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/models        0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads/suppliers     0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage                      0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/app                  0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/fonts                0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework            0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework/cache      0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework/sessions   0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework/views      0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/logs                 0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/uploads              0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/private_uploads      0700 ${user} ${group} - -"
-    ];
-
-    users = {
-      users = mkIf (user == "snipeit") {
-        snipeit = {
-          inherit group;
-          isSystemUser = true;
-        };
-        "${config.services.nginx.user}".extraGroups = [ group ];
-      };
-      groups = mkIf (group == "snipeit") {
-        snipeit = {};
-      };
-    };
-
-  };
-
-  meta.maintainers = with maintainers; [ yayayayaka ];
-}
diff --git a/nixos/modules/services/web-apps/sogo.nix b/nixos/modules/services/web-apps/sogo.nix
deleted file mode 100644
index 78b577f18f28..000000000000
--- a/nixos/modules/services/web-apps/sogo.nix
+++ /dev/null
@@ -1,271 +0,0 @@
-{ config, pkgs, lib, ... }: with lib; let
-  cfg = config.services.sogo;
-
-  preStart = pkgs.writeShellScriptBin "sogo-prestart" ''
-    touch /etc/sogo/sogo.conf
-    chown sogo:sogo /etc/sogo/sogo.conf
-    chmod 640 /etc/sogo/sogo.conf
-
-    ${if (cfg.configReplaces != {}) then ''
-      # Insert secrets
-      ${concatStringsSep "\n" (mapAttrsToList (k: v: ''export ${k}="$(cat "${v}" | tr -d '\n')"'') cfg.configReplaces)}
-
-      ${pkgs.perl}/bin/perl -p ${concatStringsSep " " (mapAttrsToList (k: v: '' -e 's/${k}/''${ENV{"${k}"}}/g;' '') cfg.configReplaces)} /etc/sogo/sogo.conf.raw > /etc/sogo/sogo.conf
-    '' else ''
-      cp /etc/sogo/sogo.conf.raw /etc/sogo/sogo.conf
-    ''}
-  '';
-
-in {
-  options.services.sogo = with types; {
-    enable = mkEnableOption "SOGo groupware";
-
-    vhostName = mkOption {
-      description = "Name of the nginx vhost";
-      type = str;
-      default = "sogo";
-    };
-
-    timezone = mkOption {
-      description = "Timezone of your SOGo instance";
-      type = str;
-      example = "America/Montreal";
-    };
-
-    language = mkOption {
-      description = "Language of SOGo";
-      type = str;
-      default = "English";
-    };
-
-    ealarmsCredFile = mkOption {
-      description = "Optional path to a credentials file for email alarms";
-      type = nullOr str;
-      default = null;
-    };
-
-    configReplaces = mkOption {
-      description = ''
-        Replacement-filepath mapping for sogo.conf.
-        Every key is replaced with the contents of the file specified as value.
-
-        In the example, every occurrence of LDAP_BINDPW will be replaced with the text of the
-        specified file.
-      '';
-      type = attrsOf str;
-      default = {};
-      example = {
-        LDAP_BINDPW = "/var/lib/secrets/sogo/ldappw";
-      };
-    };
-
-    extraConfig = mkOption {
-      description = "Extra sogo.conf configuration lines";
-      type = lines;
-      default = "";
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.sogo ];
-
-    environment.etc."sogo/sogo.conf.raw".text = ''
-      {
-        // Mandatory parameters
-        SOGoTimeZone = "${cfg.timezone}";
-        SOGoLanguage = "${cfg.language}";
-        // Paths
-        WOSendMail = "/run/wrappers/bin/sendmail";
-        SOGoMailSpoolPath = "/var/lib/sogo/spool";
-        // Enable CSRF protection
-        SOGoXSRFValidationEnabled = YES;
-        // Remove dates from log (jornald does that)
-        NGLogDefaultLogEventFormatterClass = "NGLogEventFormatter";
-        // Extra config
-        ${cfg.extraConfig}
-      }
-    '';
-
-    systemd.services.sogo = {
-      description = "SOGo groupware";
-      after = [ "postgresql.service" "mysql.service" "memcached.service" "openldap.service" "dovecot2.service" ];
-      wantedBy = [ "multi-user.target" ];
-      restartTriggers = [ config.environment.etc."sogo/sogo.conf.raw".source ];
-
-      environment.LDAPTLS_CACERT = "/etc/ssl/certs/ca-certificates.crt";
-
-      serviceConfig = {
-        Type = "forking";
-        ExecStartPre = "+" + preStart + "/bin/sogo-prestart";
-        ExecStart = "${pkgs.sogo}/bin/sogod -WOLogFile - -WOPidFile /run/sogo/sogo.pid";
-
-        ProtectSystem = "strict";
-        ProtectHome = true;
-        PrivateTmp = true;
-        PrivateDevices = true;
-        ProtectKernelTunables = true;
-        ProtectKernelModules = true;
-        ProtectControlGroups = true;
-        RuntimeDirectory = "sogo";
-        StateDirectory = "sogo/spool";
-
-        User = "sogo";
-        Group = "sogo";
-
-        CapabilityBoundingSet = "";
-        NoNewPrivileges = true;
-
-        LockPersonality = true;
-        RestrictRealtime = true;
-        PrivateMounts = true;
-        PrivateUsers = true;
-        MemoryDenyWriteExecute = true;
-        SystemCallFilter = "@basic-io @file-system @network-io @system-service @timer";
-        SystemCallArchitectures = "native";
-        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
-      };
-    };
-
-    systemd.services.sogo-tmpwatch = {
-      description = "SOGo tmpwatch";
-
-      startAt = [ "hourly" ];
-      script = ''
-        SOGOSPOOL=/var/lib/sogo/spool
-
-        find "$SOGOSPOOL" -type f -user sogo -atime +23 -delete > /dev/null
-        find "$SOGOSPOOL" -mindepth 1 -type d -user sogo -empty -delete > /dev/null
-      '';
-
-      serviceConfig = {
-        Type = "oneshot";
-
-        ProtectSystem = "strict";
-        ProtectHome = true;
-        PrivateTmp = true;
-        PrivateDevices = true;
-        ProtectKernelTunables = true;
-        ProtectKernelModules = true;
-        ProtectControlGroups = true;
-        StateDirectory = "sogo/spool";
-
-        User = "sogo";
-        Group = "sogo";
-
-        CapabilityBoundingSet = "";
-        NoNewPrivileges = true;
-
-        LockPersonality = true;
-        RestrictRealtime = true;
-        PrivateMounts = true;
-        PrivateUsers = true;
-        PrivateNetwork = true;
-        SystemCallFilter = "@basic-io @file-system @system-service";
-        SystemCallArchitectures = "native";
-        RestrictAddressFamilies = "";
-      };
-    };
-
-    systemd.services.sogo-ealarms = {
-      description = "SOGo email alarms";
-
-      after = [ "postgresql.service" "mysqld.service" "memcached.service" "openldap.service" "dovecot2.service" "sogo.service" ];
-      restartTriggers = [ config.environment.etc."sogo/sogo.conf.raw".source ];
-
-      startAt = [ "minutely" ];
-
-      serviceConfig = {
-        Type = "oneshot";
-        ExecStart = "${pkgs.sogo}/bin/sogo-ealarms-notify${optionalString (cfg.ealarmsCredFile != null) " -p ${cfg.ealarmsCredFile}"}";
-
-        ProtectSystem = "strict";
-        ProtectHome = true;
-        PrivateTmp = true;
-        PrivateDevices = true;
-        ProtectKernelTunables = true;
-        ProtectKernelModules = true;
-        ProtectControlGroups = true;
-        StateDirectory = "sogo/spool";
-
-        User = "sogo";
-        Group = "sogo";
-
-        CapabilityBoundingSet = "";
-        NoNewPrivileges = true;
-
-        LockPersonality = true;
-        RestrictRealtime = true;
-        PrivateMounts = true;
-        PrivateUsers = true;
-        MemoryDenyWriteExecute = true;
-        SystemCallFilter = "@basic-io @file-system @network-io @system-service";
-        SystemCallArchitectures = "native";
-        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
-      };
-    };
-
-    # nginx vhost
-    services.nginx.virtualHosts."${cfg.vhostName}" = {
-      locations."/".extraConfig = ''
-        rewrite ^ https://$server_name/SOGo;
-        allow all;
-      '';
-
-      # For iOS 7
-      locations."/principals/".extraConfig = ''
-        rewrite ^ https://$server_name/SOGo/dav;
-        allow all;
-      '';
-
-      locations."^~/SOGo".extraConfig = ''
-        proxy_pass http://127.0.0.1:20000;
-        proxy_redirect http://127.0.0.1:20000 default;
-
-        proxy_set_header X-Real-IP $remote_addr;
-        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-        proxy_set_header Host $host;
-        proxy_set_header x-webobjects-server-protocol HTTP/1.0;
-        proxy_set_header x-webobjects-remote-host 127.0.0.1;
-        proxy_set_header x-webobjects-server-port $server_port;
-        proxy_set_header x-webobjects-server-name $server_name;
-        proxy_set_header x-webobjects-server-url $scheme://$host;
-        proxy_connect_timeout 90;
-        proxy_send_timeout 90;
-        proxy_read_timeout 90;
-        proxy_buffer_size 64k;
-        proxy_buffers 8 64k;
-        proxy_busy_buffers_size 64k;
-        proxy_temp_file_write_size 64k;
-        client_max_body_size 50m;
-        client_body_buffer_size 128k;
-        break;
-      '';
-
-      locations."/SOGo.woa/WebServerResources/".extraConfig = ''
-        alias ${pkgs.sogo}/lib/GNUstep/SOGo/WebServerResources/;
-        allow all;
-      '';
-
-      locations."/SOGo/WebServerResources/".extraConfig = ''
-        alias ${pkgs.sogo}/lib/GNUstep/SOGo/WebServerResources/;
-        allow all;
-      '';
-
-      locations."~ ^/SOGo/so/ControlPanel/Products/([^/]*)/Resources/(.*)$".extraConfig = ''
-        alias ${pkgs.sogo}/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
-      '';
-
-      locations."~ ^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\\.(jpg|png|gif|css|js)$".extraConfig = ''
-        alias ${pkgs.sogo}/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
-      '';
-    };
-
-    # User and group
-    users.groups.sogo = {};
-    users.users.sogo = {
-      group = "sogo";
-      isSystemUser = true;
-      description = "SOGo service user";
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/suwayomi-server.md b/nixos/modules/services/web-apps/suwayomi-server.md
deleted file mode 100644
index 2185556a8721..000000000000
--- a/nixos/modules/services/web-apps/suwayomi-server.md
+++ /dev/null
@@ -1,111 +0,0 @@
-# Suwayomi-Server {#module-services-suwayomi-server}
-
-A free and open source manga reader server that runs extensions built for Tachiyomi.
-
-## Basic usage {#module-services-suwayomi-server-basic-usage}
-
-By default, the module will execute Suwayomi-Server backend and web UI:
-
-```nix
-{ ... }:
-
-{
-  services.suwayomi-server = {
-    enable = true;
-  };
-}
-```
-
-It runs in the systemd service named `suwayomi-server` in the data directory `/var/lib/suwayomi-server`.
-
-You can change the default parameters with some other parameters:
-```nix
-{ ... }:
-
-{
-  services.suwayomi-server = {
-    enable = true;
-
-    dataDir = "/var/lib/suwayomi"; # Default is "/var/lib/suwayomi-server"
-    openFirewall = true;
-
-    settings = {
-      server.port = 4567;
-    };
-  };
-}
-```
-
-If you want to create a desktop icon, you can activate the system tray option:
-
-```nix
-{ ... }:
-
-{
-  services.suwayomi-server = {
-    enable = true;
-
-    dataDir = "/var/lib/suwayomi"; # Default is "/var/lib/suwayomi-server"
-    openFirewall = true;
-
-    settings = {
-      server.port = 4567;
-      server.enableSystemTray = true;
-    };
-  };
-}
-```
-
-## Basic authentication {#module-services-suwayomi-server-basic-auth}
-
-You can configure a basic authentication to the web interface with:
-
-```nix
-{ ... }:
-
-{
-  services.suwayomi-server = {
-    enable = true;
-
-    openFirewall = true;
-
-    settings = {
-      server.port = 4567;
-      server = {
-        basicAuthEnabled = true;
-        basicAuthUsername = "username";
-
-        # NOTE: this is not a real upstream option
-        basicAuthPasswordFile = ./path/to/the/password/file;
-      };
-    };
-  };
-}
-```
-
-## Extra configuration {#module-services-suwayomi-server-extra-config}
-
-Not all the configuration options are available directly in this module, but you can add the other options of suwayomi-server with:
-
-```nix
-{ ... }:
-
-{
-  services.suwayomi-server = {
-    enable = true;
-
-    openFirewall = true;
-
-    settings = {
-      server = {
-        port = 4567;
-        autoDownloadNewChapters = false;
-        maxSourcesInParallel = 6;
-        extensionRepos = [
-          "https://raw.githubusercontent.com/MY_ACCOUNT/MY_REPO/repo/index.min.json"
-        ];
-      };
-    };
-  };
-}
-```
diff --git a/nixos/modules/services/web-apps/suwayomi-server.nix b/nixos/modules/services/web-apps/suwayomi-server.nix
deleted file mode 100644
index 5b61852a534d..000000000000
--- a/nixos/modules/services/web-apps/suwayomi-server.nix
+++ /dev/null
@@ -1,226 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-  cfg = config.services.suwayomi-server;
-  inherit (lib) mkOption mkEnableOption mkIf types;
-
-  format = pkgs.formats.hocon { };
-in
-{
-  options = {
-    services.suwayomi-server = {
-      enable = mkEnableOption "Suwayomi, a free and open source manga reader server that runs extensions built for Tachiyomi.";
-
-      package = lib.mkPackageOptionMD pkgs "suwayomi-server" { };
-
-      dataDir = mkOption {
-        type = types.path;
-        default = "/var/lib/suwayomi-server";
-        example = "/var/data/mangas";
-        description = ''
-          The path to the data directory in which Suwayomi-Server will download scans.
-        '';
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "suwayomi";
-        example = "root";
-        description = ''
-          User account under which Suwayomi-Server runs.
-        '';
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "suwayomi";
-        example = "medias";
-        description = ''
-          Group under which Suwayomi-Server runs.
-        '';
-      };
-
-      openFirewall = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to open the firewall for the port in {option}`services.suwayomi-server.settings.server.port`.
-        '';
-      };
-
-      settings = mkOption {
-        type = types.submodule {
-          freeformType = format.type;
-          options = {
-            server = {
-              ip = mkOption {
-                type = types.str;
-                default = "0.0.0.0";
-                example = "127.0.0.1";
-                description = ''
-                  The ip that Suwayomi will bind to.
-                '';
-              };
-
-              port = mkOption {
-                type = types.port;
-                default = 8080;
-                example = 4567;
-                description = ''
-                  The port that Suwayomi will listen to.
-                '';
-              };
-
-              basicAuthEnabled = mkEnableOption ''
-                Add basic access authentication to Suwayomi-Server.
-                Enabling this option is useful when hosting on a public network/the Internet
-              '';
-
-              basicAuthUsername = mkOption {
-                type = types.nullOr types.str;
-                default = null;
-                description = ''
-                  The username value that you have to provide when authenticating.
-                '';
-              };
-
-              # NOTE: this is not a real upstream option
-              basicAuthPasswordFile = mkOption {
-                type = types.nullOr types.path;
-                default = null;
-                example = "/var/secrets/suwayomi-server-password";
-                description = ''
-                  The password file containing the value that you have to provide when authenticating.
-                '';
-              };
-
-              downloadAsCbz = mkOption {
-                type = types.bool;
-                default = false;
-                description = ''
-                  Download chapters as `.cbz` files.
-                '';
-              };
-
-              extensionRepos = mkOption {
-                type = types.listOf types.str;
-                default = [];
-                example = [
-                  "https://raw.githubusercontent.com/MY_ACCOUNT/MY_REPO/repo/index.min.json"
-                ];
-                description = ''
-                  URL of repositories from which the extensions can be installed.
-                '';
-              };
-
-              localSourcePath = mkOption {
-                type = types.path;
-                default = cfg.dataDir;
-                defaultText = lib.literalExpression "suwayomi-server.dataDir";
-                example = "/var/data/local_mangas";
-                description = ''
-                  Path to the local source folder.
-                '';
-              };
-
-              systemTrayEnabled = mkOption {
-                type = types.bool;
-                default = false;
-                description = ''
-                  Whether to enable a system tray icon, if possible.
-                '';
-              };
-            };
-          };
-        };
-        description = ''
-          Configuration to write to {file}`server.conf`.
-          See <https://github.com/Suwayomi/Suwayomi-Server/wiki/Configuring-Suwayomi-Server> for more information.
-        '';
-        default = { };
-        example = {
-          server.socksProxyEnabled = true;
-          server.socksProxyHost = "yourproxyhost.com";
-          server.socksProxyPort = "8080";
-        };
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    assertions = [{
-      assertion = with cfg.settings.server; basicAuthEnabled -> (basicAuthUsername != null && basicAuthPasswordFile != null);
-      message = ''
-        [suwayomi-server]: the username and the password file cannot be null when the basic auth is enabled
-      '';
-    }];
-
-    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.server.port ];
-
-    users.groups = mkIf (cfg.group == "suwayomi") {
-      suwayomi = { };
-    };
-
-    users.users = mkIf (cfg.user == "suwayomi") {
-      suwayomi = {
-        group = cfg.group;
-        # Need to set the user home because the package writes to ~/.local/Tachidesk
-        home = cfg.dataDir;
-        description = "Suwayomi Daemon user";
-        isSystemUser = true;
-      };
-    };
-
-    systemd.tmpfiles.settings."10-suwayomi-server" = {
-      "${cfg.dataDir}/.local/share/Tachidesk".d = {
-        mode = "0700";
-        inherit (cfg) user group;
-      };
-    };
-
-    systemd.services.suwayomi-server =
-      let
-        configFile = format.generate "server.conf" (lib.pipe cfg.settings [
-          (settings: lib.recursiveUpdate settings {
-            server.basicAuthPasswordFile = null;
-            server.basicAuthPassword =
-              if settings.server.basicAuthEnabled
-              then "$TACHIDESK_SERVER_BASIC_AUTH_PASSWORD"
-              else null;
-          })
-          (lib.filterAttrsRecursive (_: x: x != null))
-        ]);
-      in
-      {
-        description = "A free and open source manga reader server that runs extensions built for Tachiyomi.";
-
-        wantedBy = [ "multi-user.target" ];
-        wants = [ "network-online.target" ];
-        after = [ "network-online.target" ];
-
-        script = ''
-          ${lib.optionalString cfg.settings.server.basicAuthEnabled ''
-            export TACHIDESK_SERVER_BASIC_AUTH_PASSWORD="$(<${cfg.settings.server.basicAuthPasswordFile})"
-          ''}
-          ${lib.getExe pkgs.envsubst} -i ${configFile} -o ${cfg.dataDir}/.local/share/Tachidesk/server.conf
-          ${lib.getExe cfg.package} -Dsuwayomi.tachidesk.config.server.rootDir=${cfg.dataDir}
-        '';
-
-        serviceConfig = {
-          User = cfg.user;
-          Group = cfg.group;
-
-          Type = "simple";
-          Restart = "on-failure";
-
-          StateDirectory = mkIf (cfg.dataDir == "/var/lib/suwayomi-server") "suwayomi-server";
-        };
-      };
-  };
-
-  meta = {
-    maintainers = with lib.maintainers; [ ratcornu ];
-    doc = ./suwayomi-server.md;
-  };
-}
diff --git a/nixos/modules/services/web-apps/trilium.nix b/nixos/modules/services/web-apps/trilium.nix
deleted file mode 100644
index 42b0a16827c3..000000000000
--- a/nixos/modules/services/web-apps/trilium.nix
+++ /dev/null
@@ -1,155 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.trilium-server;
-  configIni = pkgs.writeText "trilium-config.ini" ''
-    [General]
-    # Instance name can be used to distinguish between different instances
-    instanceName=${cfg.instanceName}
-
-    # Disable automatically generating desktop icon
-    noDesktopIcon=true
-    noBackup=${lib.boolToString cfg.noBackup}
-    noAuthentication=${lib.boolToString cfg.noAuthentication}
-
-    [Network]
-    # host setting is relevant only for web deployments - set the host on which the server will listen
-    host=${cfg.host}
-    # port setting is relevant only for web deployments, desktop builds run on random free port
-    port=${toString cfg.port}
-    # true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure).
-    https=false
-  '';
-in
-{
-
-  options.services.trilium-server = with lib; {
-    enable = mkEnableOption "trilium-server";
-
-    dataDir = mkOption {
-      type = types.str;
-      default = "/var/lib/trilium";
-      description = ''
-        The directory storing the notes database and the configuration.
-      '';
-    };
-
-    instanceName = mkOption {
-      type = types.str;
-      default = "Trilium";
-      description = ''
-        Instance name used to distinguish between different instances
-      '';
-    };
-
-    noBackup = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Disable periodic database backups.
-      '';
-    };
-
-    noAuthentication = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        If set to true, no password is required to access the web frontend.
-      '';
-    };
-
-    host = mkOption {
-      type = types.str;
-      default = "127.0.0.1";
-      description = ''
-        The host address to bind to (defaults to localhost).
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 8080;
-      description = ''
-        The port number to bind to.
-      '';
-    };
-
-    nginx = mkOption {
-      default = {};
-      description = ''
-        Configuration for nginx reverse proxy.
-      '';
-
-      type = types.submodule {
-        options = {
-          enable = mkOption {
-            type = types.bool;
-            default = false;
-            description = ''
-              Configure the nginx reverse proxy settings.
-            '';
-          };
-
-          hostName = mkOption {
-            type = types.str;
-            description = ''
-              The hostname use to setup the virtualhost configuration
-            '';
-          };
-        };
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable (lib.mkMerge [
-  {
-    meta.maintainers = with lib.maintainers; [ fliegendewurst ];
-
-    users.groups.trilium = {};
-    users.users.trilium = {
-      description = "Trilium User";
-      group = "trilium";
-      home = cfg.dataDir;
-      isSystemUser = true;
-    };
-
-    systemd.services.trilium-server = {
-      wantedBy = [ "multi-user.target" ];
-      environment.TRILIUM_DATA_DIR = cfg.dataDir;
-      serviceConfig = {
-        ExecStart = "${pkgs.trilium-server}/bin/trilium-server";
-        User = "trilium";
-        Group = "trilium";
-        PrivateTmp = "true";
-      };
-    };
-
-    systemd.tmpfiles.rules = [
-      "d  ${cfg.dataDir}            0750 trilium trilium - -"
-      "L+ ${cfg.dataDir}/config.ini -    -       -       - ${configIni}"
-    ];
-
-  }
-
-  (lib.mkIf cfg.nginx.enable {
-    services.nginx = {
-      enable = true;
-      virtualHosts."${cfg.nginx.hostName}" = {
-        locations."/" = {
-          proxyPass = "http://${cfg.host}:${toString cfg.port}/";
-          extraConfig = ''
-            proxy_http_version 1.1;
-            proxy_set_header Upgrade $http_upgrade;
-            proxy_set_header Connection 'upgrade';
-            proxy_set_header Host $host;
-            proxy_cache_bypass $http_upgrade;
-          '';
-        };
-        extraConfig = ''
-          client_max_body_size 0;
-        '';
-      };
-    };
-  })
-  ]);
-}
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
deleted file mode 100644
index 9826febb3c66..000000000000
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ /dev/null
@@ -1,669 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.tt-rss;
-
-  inherit (cfg) phpPackage;
-
-  configVersion = 26;
-
-  dbPort = if cfg.database.port == null
-    then (if cfg.database.type == "pgsql" then 5432 else 3306)
-    else cfg.database.port;
-
-  poolName = "tt-rss";
-
-  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
-  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
-
-  tt-rss-config = let
-    password =
-      if (cfg.database.password != null) then
-        "'${(escape ["'" "\\"] cfg.database.password)}'"
-      else if (cfg.database.passwordFile != null) then
-        "file_get_contents('${cfg.database.passwordFile}')"
-      else
-        null
-      ;
-  in pkgs.writeText "config.php" ''
-    <?php
-      putenv('TTRSS_PHP_EXECUTABLE=${phpPackage}/bin/php');
-
-      putenv('TTRSS_LOCK_DIRECTORY=${cfg.root}/lock');
-      putenv('TTRSS_CACHE_DIR=${cfg.root}/cache');
-      putenv('TTRSS_ICONS_DIR=${cfg.root}/feed-icons');
-      putenv('TTRSS_ICONS_URL=feed-icons');
-      putenv('TTRSS_SELF_URL_PATH=${cfg.selfUrlPath}');
-
-      putenv('TTRSS_MYSQL_CHARSET=UTF8');
-
-      putenv('TTRSS_DB_TYPE=${cfg.database.type}');
-      putenv('TTRSS_DB_HOST=${optionalString (cfg.database.host != null) cfg.database.host}');
-      putenv('TTRSS_DB_USER=${cfg.database.user}');
-      putenv('TTRSS_DB_NAME=${cfg.database.name}');
-      putenv('TTRSS_DB_PASS=' ${optionalString (password != null) ". ${password}"});
-      putenv('TTRSS_DB_PORT=${toString dbPort}');
-
-      putenv('TTRSS_AUTH_AUTO_CREATE=${boolToString cfg.auth.autoCreate}');
-      putenv('TTRSS_AUTH_AUTO_LOGIN=${boolToString cfg.auth.autoLogin}');
-
-      putenv('TTRSS_FEED_CRYPT_KEY=${escape ["'" "\\"] cfg.feedCryptKey}');
-
-
-      putenv('TTRSS_SINGLE_USER_MODE=${boolToString cfg.singleUserMode}');
-
-      putenv('TTRSS_SIMPLE_UPDATE_MODE=${boolToString cfg.simpleUpdateMode}');
-
-      # Never check for updates - the running version of the code should
-      # be controlled entirely by the version of TT-RSS active in the
-      # current Nix profile. If TT-RSS updates itself to a version
-      # requiring a database schema upgrade, and then the SystemD
-      # tt-rss.service is restarted, the old code copied from the Nix
-      # store will overwrite the updated version, causing the code to
-      # detect the need for a schema "upgrade" (since the schema version
-      # in the database is different than in the code), but the update
-      # schema operation in TT-RSS will do nothing because the schema
-      # version in the database is newer than that in the code.
-      putenv('TTRSS_CHECK_FOR_UPDATES=false');
-
-      putenv('TTRSS_FORCE_ARTICLE_PURGE=${toString cfg.forceArticlePurge}');
-      putenv('TTRSS_SESSION_COOKIE_LIFETIME=${toString cfg.sessionCookieLifetime}');
-      putenv('TTRSS_ENABLE_GZIP_OUTPUT=${boolToString cfg.enableGZipOutput}');
-
-      putenv('TTRSS_PLUGINS=${builtins.concatStringsSep "," cfg.plugins}');
-
-      putenv('TTRSS_LOG_DESTINATION=${cfg.logDestination}');
-      putenv('TTRSS_CONFIG_VERSION=${toString configVersion}');
-
-
-      putenv('TTRSS_PUBSUBHUBBUB_ENABLED=${boolToString cfg.pubSubHubbub.enable}');
-      putenv('TTRSS_PUBSUBHUBBUB_HUB=${cfg.pubSubHubbub.hub}');
-
-      putenv('TTRSS_SPHINX_SERVER=${cfg.sphinx.server}');
-      putenv('TTRSS_SPHINX_INDEX=${builtins.concatStringsSep "," cfg.sphinx.index}');
-
-      putenv('TTRSS_ENABLE_REGISTRATION=${boolToString cfg.registration.enable}');
-      putenv('TTRSS_REG_NOTIFY_ADDRESS=${cfg.registration.notifyAddress}');
-      putenv('TTRSS_REG_MAX_USERS=${toString cfg.registration.maxUsers}');
-
-      putenv('TTRSS_SMTP_SERVER=${cfg.email.server}');
-      putenv('TTRSS_SMTP_LOGIN=${cfg.email.login}');
-      putenv('TTRSS_SMTP_PASSWORD=${escape ["'" "\\"] cfg.email.password}');
-      putenv('TTRSS_SMTP_SECURE=${cfg.email.security}');
-
-      putenv('TTRSS_SMTP_FROM_NAME=${escape ["'" "\\"] cfg.email.fromName}');
-      putenv('TTRSS_SMTP_FROM_ADDRESS=${escape ["'" "\\"] cfg.email.fromAddress}');
-      putenv('TTRSS_DIGEST_SUBJECT=${escape ["'" "\\"] cfg.email.digestSubject}');
-
-      ${cfg.extraConfig}
-  '';
-
-  # tt-rss and plugins and themes and config.php
-  servedRoot = pkgs.runCommand "tt-rss-served-root" {} ''
-    cp --no-preserve=mode -r ${pkgs.tt-rss} $out
-    cp ${tt-rss-config} $out/config.php
-    ${optionalString (cfg.pluginPackages != []) ''
-    for plugin in ${concatStringsSep " " cfg.pluginPackages}; do
-    cp -r "$plugin"/* "$out/plugins.local/"
-    done
-    ''}
-    ${optionalString (cfg.themePackages != []) ''
-    for theme in ${concatStringsSep " " cfg.themePackages}; do
-    cp -r "$theme"/* "$out/themes.local/"
-    done
-    ''}
-  '';
-
- in {
-
-  ###### interface
-
-  options = {
-
-    services.tt-rss = {
-
-      enable = mkEnableOption "tt-rss";
-
-      root = mkOption {
-        type = types.path;
-        default = "/var/lib/tt-rss";
-        description = ''
-          Root of the application.
-        '';
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "tt_rss";
-        description = ''
-          User account under which both the update daemon and the web-application run.
-        '';
-      };
-
-      pool = mkOption {
-        type = types.str;
-        default = "${poolName}";
-        description = ''
-          Name of existing phpfpm pool that is used to run web-application.
-          If not specified a pool will be created automatically with
-          default values.
-        '';
-      };
-
-      virtualHost = mkOption {
-        type = types.nullOr types.str;
-        default = "tt-rss";
-        description = ''
-          Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
-        '';
-      };
-
-      database = {
-        type = mkOption {
-          type = types.enum ["pgsql" "mysql"];
-          default = "pgsql";
-          description = ''
-            Database to store feeds. Supported are pgsql and mysql.
-          '';
-        };
-
-        host = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          description = ''
-            Host of the database. Leave null to use Unix domain socket.
-          '';
-        };
-
-        name = mkOption {
-          type = types.str;
-          default = "tt_rss";
-          description = ''
-            Name of the existing database.
-          '';
-        };
-
-        user = mkOption {
-          type = types.str;
-          default = "tt_rss";
-          description = ''
-            The database user. The user must exist and has access to
-            the specified database.
-          '';
-        };
-
-        password = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          description = ''
-            The database user's password.
-          '';
-        };
-
-        passwordFile = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          description = ''
-            The database user's password.
-          '';
-        };
-
-        port = mkOption {
-          type = types.nullOr types.port;
-          default = null;
-          description = ''
-            The database's port. If not set, the default ports will be provided (5432
-            and 3306 for pgsql and mysql respectively).
-          '';
-        };
-
-        createLocally = mkOption {
-          type = types.bool;
-          default = true;
-          description = "Create the database and database user locally.";
-        };
-      };
-
-      auth = {
-        autoCreate = mkOption {
-          type = types.bool;
-          default = true;
-          description = ''
-            Allow authentication modules to auto-create users in tt-rss internal
-            database when authenticated successfully.
-          '';
-        };
-
-        autoLogin = mkOption {
-          type = types.bool;
-          default = true;
-          description = ''
-            Automatically login user on remote or other kind of externally supplied
-            authentication, otherwise redirect to login form as normal.
-            If set to true, users won't be able to set application language
-            and settings profile.
-          '';
-        };
-      };
-
-      pubSubHubbub = {
-        hub = mkOption {
-          type = types.str;
-          default = "";
-          description = ''
-            URL to a PubSubHubbub-compatible hub server. If defined, "Published
-            articles" generated feed would automatically become PUSH-enabled.
-          '';
-        };
-
-        enable = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Enable client PubSubHubbub support in tt-rss. When disabled, tt-rss
-            won't try to subscribe to PUSH feed updates.
-          '';
-        };
-      };
-
-      sphinx = {
-        server = mkOption {
-          type = types.str;
-          default = "localhost:9312";
-          description = ''
-            Hostname:port combination for the Sphinx server.
-          '';
-        };
-
-        index = mkOption {
-          type = types.listOf types.str;
-          default = ["ttrss" "delta"];
-          description = ''
-            Index names in Sphinx configuration. Example configuration
-            files are available on tt-rss wiki.
-          '';
-        };
-      };
-
-      registration = {
-        enable = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Allow users to register themselves. Please be aware that allowing
-            random people to access your tt-rss installation is a security risk
-            and potentially might lead to data loss or server exploit. Disabled
-            by default.
-          '';
-        };
-
-        notifyAddress = mkOption {
-          type = types.str;
-          default = "";
-          description = ''
-            Email address to send new user notifications to.
-          '';
-        };
-
-        maxUsers = mkOption {
-          type = types.int;
-          default = 0;
-          description = ''
-            Maximum amount of users which will be allowed to register on this
-            system. 0 - no limit.
-          '';
-        };
-      };
-
-      email = {
-        server = mkOption {
-          type = types.str;
-          default = "";
-          example = "localhost:25";
-          description = ''
-            Hostname:port combination to send outgoing mail. Blank - use system
-            MTA.
-          '';
-        };
-
-        login = mkOption {
-          type = types.str;
-          default = "";
-          description = ''
-            SMTP authentication login used when sending outgoing mail.
-          '';
-        };
-
-        password = mkOption {
-          type = types.str;
-          default = "";
-          description = ''
-            SMTP authentication password used when sending outgoing mail.
-          '';
-        };
-
-        security = mkOption {
-          type = types.enum ["" "ssl" "tls"];
-          default = "";
-          description = ''
-            Used to select a secure SMTP connection. Allowed values: ssl, tls,
-            or empty.
-          '';
-        };
-
-        fromName = mkOption {
-          type = types.str;
-          default = "Tiny Tiny RSS";
-          description = ''
-            Name for sending outgoing mail. This applies to password reset
-            notifications, digest emails and any other mail.
-          '';
-        };
-
-        fromAddress = mkOption {
-          type = types.str;
-          default = "";
-          description = ''
-            Address for sending outgoing mail. This applies to password reset
-            notifications, digest emails and any other mail.
-          '';
-        };
-
-        digestSubject = mkOption {
-          type = types.str;
-          default = "[tt-rss] New headlines for last 24 hours";
-          description = ''
-            Subject line for email digests.
-          '';
-        };
-      };
-
-      sessionCookieLifetime = mkOption {
-        type = types.int;
-        default = 86400;
-        description = ''
-          Default lifetime of a session (e.g. login) cookie. In seconds,
-          0 means cookie will be deleted when browser closes.
-        '';
-      };
-
-      selfUrlPath = mkOption {
-        type = types.str;
-        description = ''
-          Full URL of your tt-rss installation. This should be set to the
-          location of tt-rss directory, e.g. http://example.org/tt-rss/
-          You need to set this option correctly otherwise several features
-          including PUSH, bookmarklets and browser integration will not work properly.
-        '';
-        example = "http://localhost";
-      };
-
-      feedCryptKey = mkOption {
-        type = types.str;
-        default = "";
-        description = ''
-          Key used for encryption of passwords for password-protected feeds
-          in the database. A string of 24 random characters. If left blank, encryption
-          is not used. Requires mcrypt functions.
-          Warning: changing this key will make your stored feed passwords impossible
-          to decrypt.
-        '';
-      };
-
-      singleUserMode = mkOption {
-        type = types.bool;
-        default = false;
-
-        description = ''
-          Operate in single user mode, disables all functionality related to
-          multiple users and authentication. Enabling this assumes you have
-          your tt-rss directory protected by other means (e.g. http auth).
-        '';
-      };
-
-      simpleUpdateMode = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enables fallback update mode where tt-rss tries to update feeds in
-          background while tt-rss is open in your browser.
-          If you don't have a lot of feeds and don't want to or can't run
-          background processes while not running tt-rss, this method is generally
-          viable to keep your feeds up to date.
-          Still, there are more robust (and recommended) updating methods
-          available, you can read about them here: <https://tt-rss.org/wiki/UpdatingFeeds>
-        '';
-      };
-
-      forceArticlePurge = mkOption {
-        type = types.int;
-        default = 0;
-        description = ''
-          When this option is not 0, users ability to control feed purging
-          intervals is disabled and all articles (which are not starred)
-          older than this amount of days are purged.
-        '';
-      };
-
-      enableGZipOutput = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Selectively gzip output to improve wire performance. This requires
-          PHP Zlib extension on the server.
-          Enabling this can break tt-rss in several httpd/php configurations,
-          if you experience weird errors and tt-rss failing to start, blank pages
-          after login, or content encoding errors, disable it.
-        '';
-      };
-
-      phpPackage = lib.mkOption {
-        type = lib.types.package;
-        default = pkgs.php;
-        defaultText = "pkgs.php";
-        description = ''
-          php package to use for php fpm and update daemon.
-        '';
-      };
-
-      plugins = mkOption {
-        type = types.listOf types.str;
-        default = ["auth_internal" "note"];
-        description = ''
-          List of plugins to load automatically for all users.
-          System plugins have to be specified here. Please enable at least one
-          authentication plugin here (auth_*).
-          Users may enable other user plugins from Preferences/Plugins but may not
-          disable plugins specified in this list.
-          Disabling auth_internal in this list would automatically disable
-          reset password link on the login form.
-        '';
-      };
-
-      pluginPackages = mkOption {
-        type = types.listOf types.package;
-        default = [];
-        description = ''
-          List of plugins to install. The list elements are expected to
-          be derivations. All elements in this derivation are automatically
-          copied to the `plugins.local` directory.
-        '';
-      };
-
-      themePackages = mkOption {
-        type = types.listOf types.package;
-        default = [];
-        description = ''
-          List of themes to install. The list elements are expected to
-          be derivations. All elements in this derivation are automatically
-          copied to the `themes.local` directory.
-        '';
-      };
-
-      logDestination = mkOption {
-        type = types.enum ["" "sql" "syslog"];
-        default = "sql";
-        description = ''
-          Log destination to use. Possible values: sql (uses internal logging
-          you can read in Preferences -> System), syslog - logs to system log.
-          Setting this to blank uses PHP logging (usually to http server
-          error.log).
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = ''
-          Additional lines to append to `config.php`.
-        '';
-      };
-    };
-  };
-
-  imports = [
-    (mkRemovedOptionModule ["services" "tt-rss" "checkForUpdates"] ''
-      This option was removed because setting this to true will cause TT-RSS
-      to be unable to start if an automatic update of the code in
-      services.tt-rss.root leads to a database schema upgrade that is not
-      supported by the code active in the Nix store.
-    '')
-  ];
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    assertions = [
-      {
-        assertion = cfg.database.password != null -> cfg.database.passwordFile == null;
-        message = "Cannot set both password and passwordFile";
-      }
-      {
-        assertion = cfg.database.createLocally -> cfg.database.name == cfg.user && cfg.database.user == cfg.user;
-        message = ''
-          When creating a database via NixOS, the db user and db name must be equal!
-          If you already have an existing DB+user and this assertion is new, you can safely set
-          `services.tt-rss.database.createLocally` to `false` because removal of `ensureUsers`
-          and `ensureDatabases` doesn't have any effect.
-        '';
-      }
-    ];
-
-    services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
-      ${poolName} = {
-        inherit (cfg) user;
-        inherit phpPackage;
-        settings = mapAttrs (name: mkDefault) {
-          "listen.owner" = "nginx";
-          "listen.group" = "nginx";
-          "listen.mode" = "0600";
-          "pm" = "dynamic";
-          "pm.max_children" = 75;
-          "pm.start_servers" = 10;
-          "pm.min_spare_servers" = 5;
-          "pm.max_spare_servers" = 20;
-          "pm.max_requests" = 500;
-          "catch_workers_output" = 1;
-        };
-      };
-    };
-
-    # NOTE: No configuration is done if not using virtual host
-    services.nginx = mkIf (cfg.virtualHost != null) {
-      enable = true;
-      virtualHosts = {
-        ${cfg.virtualHost} = {
-          root = "${cfg.root}/www";
-
-          locations."/" = {
-            index = "index.php";
-          };
-
-          locations."^~ /feed-icons" = {
-            root = "${cfg.root}";
-          };
-
-          locations."~ \\.php$" = {
-            extraConfig = ''
-              fastcgi_split_path_info ^(.+\.php)(/.+)$;
-              fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket};
-              fastcgi_index index.php;
-            '';
-          };
-        };
-      };
-    };
-
-    systemd.tmpfiles.rules = [
-      "d '${cfg.root}' 0555 ${cfg.user} tt_rss - -"
-      "d '${cfg.root}/lock' 0755 ${cfg.user} tt_rss - -"
-      "d '${cfg.root}/cache' 0755 ${cfg.user} tt_rss - -"
-      "d '${cfg.root}/cache/upload' 0755 ${cfg.user} tt_rss - -"
-      "d '${cfg.root}/cache/images' 0755 ${cfg.user} tt_rss - -"
-      "d '${cfg.root}/cache/export' 0755 ${cfg.user} tt_rss - -"
-      "d '${cfg.root}/feed-icons' 0755 ${cfg.user} tt_rss - -"
-      "L+ '${cfg.root}/www' - - - - ${servedRoot}"
-    ];
-
-    systemd.services = {
-      phpfpm-tt-rss = mkIf (cfg.pool == "${poolName}") {
-        restartTriggers = [ servedRoot ];
-      };
-
-      tt-rss = {
-        description = "Tiny Tiny RSS feeds update daemon";
-
-        preStart = ''
-          ${phpPackage}/bin/php ${cfg.root}/www/update.php --update-schema --force-yes
-        '';
-
-        serviceConfig = {
-          User = "${cfg.user}";
-          Group = "tt_rss";
-          ExecStart = "${phpPackage}/bin/php ${cfg.root}/www/update.php --daemon --quiet";
-          Restart = "on-failure";
-          RestartSec = "60";
-          SyslogIdentifier = "tt-rss";
-        };
-
-        wantedBy = [ "multi-user.target" ];
-        requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
-        after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
-      };
-    };
-
-    services.mysql = mkIf mysqlLocal {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        {
-          name = cfg.user;
-          ensurePermissions = {
-            "${cfg.database.name}.*" = "ALL PRIVILEGES";
-          };
-        }
-      ];
-    };
-
-    services.postgresql = mkIf pgsqlLocal {
-      enable = mkDefault true;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        { name = cfg.database.user;
-          ensureDBOwnership = true;
-        }
-      ];
-    };
-
-    users.users.tt_rss = optionalAttrs (cfg.user == "tt_rss") {
-      description = "tt-rss service user";
-      isSystemUser = true;
-      group = "tt_rss";
-    };
-
-    users.groups.tt_rss = {};
-  };
-}
diff --git a/nixos/modules/services/web-apps/vikunja.nix b/nixos/modules/services/web-apps/vikunja.nix
deleted file mode 100644
index 9727eaccc1d0..000000000000
--- a/nixos/modules/services/web-apps/vikunja.nix
+++ /dev/null
@@ -1,118 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-with lib;
-
-let
-  cfg = config.services.vikunja;
-  format = pkgs.formats.yaml {};
-  configFile = format.generate "config.yaml" cfg.settings;
-  useMysql = cfg.database.type == "mysql";
-  usePostgresql = cfg.database.type == "postgres";
-in {
-  imports = [
-    (mkRemovedOptionModule [ "services" "vikunja" "setupNginx" ] "services.vikunja no longer supports the automatic set up of a nginx virtual host. Set up your own webserver config with a proxy pass to the vikunja service.")
-  ];
-
-  options.services.vikunja = with lib; {
-    enable = mkEnableOption "vikunja service";
-    package = mkPackageOption pkgs "vikunja" { };
-    environmentFiles = mkOption {
-      type = types.listOf types.path;
-      default = [ ];
-      description = ''
-        List of environment files set in the vikunja systemd service.
-        For example passwords should be set in one of these files.
-      '';
-    };
-    frontendScheme = mkOption {
-      type = types.enum [ "http" "https" ];
-      description = ''
-        Whether the site is available via http or https.
-      '';
-    };
-    frontendHostname = mkOption {
-      type = types.str;
-      description = "The Hostname under which the frontend is running.";
-    };
-    port = mkOption {
-      type = types.port;
-      default = 3456;
-      description = "The TCP port exposed by the API.";
-    };
-
-    settings = mkOption {
-      type = format.type;
-      default = {};
-      description = ''
-        Vikunja configuration. Refer to
-        <https://vikunja.io/docs/config-options/>
-        for details on supported values.
-        '';
-    };
-    database = {
-      type = mkOption {
-        type = types.enum [ "sqlite" "mysql" "postgres" ];
-        example = "postgres";
-        default = "sqlite";
-        description = "Database engine to use.";
-      };
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Database host address. Can also be a socket.";
-      };
-      user = mkOption {
-        type = types.str;
-        default = "vikunja";
-        description = "Database user.";
-      };
-      database = mkOption {
-        type = types.str;
-        default = "vikunja";
-        description = "Database name.";
-      };
-      path = mkOption {
-        type = types.str;
-        default = "/var/lib/vikunja/vikunja.db";
-        description = "Path to the sqlite3 database file.";
-      };
-    };
-  };
-  config = lib.mkIf cfg.enable {
-    services.vikunja.settings = {
-      database = {
-        inherit (cfg.database) type host user database path;
-      };
-      service = {
-        interface = ":${toString cfg.port}";
-        frontendurl = "${cfg.frontendScheme}://${cfg.frontendHostname}/";
-      };
-      files = {
-        basepath = "/var/lib/vikunja/files";
-      };
-    };
-
-    systemd.services.vikunja = {
-      description = "vikunja";
-      after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
-      wantedBy = [ "multi-user.target" ];
-      path = [ cfg.package ];
-      restartTriggers = [ configFile ];
-
-      serviceConfig = {
-        Type = "simple";
-        DynamicUser = true;
-        StateDirectory = "vikunja";
-        ExecStart = "${cfg.package}/bin/vikunja";
-        Restart = "always";
-        EnvironmentFile = cfg.environmentFiles;
-      };
-    };
-
-    environment.etc."vikunja/config.yaml".source = configFile;
-
-    environment.systemPackages = [
-      cfg.package # for admin `vikunja` CLI
-    ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/whitebophir.nix b/nixos/modules/services/web-apps/whitebophir.nix
deleted file mode 100644
index 332a8d9d4ec6..000000000000
--- a/nixos/modules/services/web-apps/whitebophir.nix
+++ /dev/null
@@ -1,47 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.whitebophir;
-in {
-  options = {
-    services.whitebophir = {
-      enable = mkEnableOption "whitebophir, an online collaborative whiteboard server (persistent state will be maintained under {file}`/var/lib/whitebophir`)";
-
-      package = mkPackageOption pkgs "whitebophir" { };
-
-      listenAddress = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
-        description = "Address to listen on (use 0.0.0.0 to allow access from any address).";
-      };
-
-      port = mkOption {
-        type = types.port;
-        default = 5001;
-        description = "Port to bind to.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.whitebophir = {
-      description = "Whitebophir Service";
-      wantedBy    = [ "multi-user.target" ];
-      after       = [ "network.target" ];
-      environment = {
-        PORT            = toString cfg.port;
-        HOST            = toString cfg.listenAddress;
-        WBO_HISTORY_DIR = "/var/lib/whitebophir";
-      };
-
-      serviceConfig = {
-        DynamicUser    = true;
-        ExecStart      = "${cfg.package}/bin/whitebophir";
-        Restart        = "always";
-        StateDirectory = "whitebophir";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/wiki-js.nix b/nixos/modules/services/web-apps/wiki-js.nix
deleted file mode 100644
index dedc4c584628..000000000000
--- a/nixos/modules/services/web-apps/wiki-js.nix
+++ /dev/null
@@ -1,142 +0,0 @@
-{ lib, pkgs, config, ... }:
-
-with lib;
-
-let
-  cfg = config.services.wiki-js;
-
-  format = pkgs.formats.json { };
-
-  configFile = format.generate "wiki-js.yml" cfg.settings;
-in {
-  options.services.wiki-js = {
-    enable = mkEnableOption "wiki-js";
-
-    environmentFile = mkOption {
-      type = types.nullOr types.path;
-      default = null;
-      example = "/root/wiki-js.env";
-      description = ''
-        Environment file to inject e.g. secrets into the configuration.
-      '';
-    };
-
-    stateDirectoryName = mkOption {
-      default = "wiki-js";
-      type = types.str;
-      description = ''
-        Name of the directory in {file}`/var/lib`.
-      '';
-    };
-
-    settings = mkOption {
-      default = {};
-      type = types.submodule {
-        freeformType = format.type;
-        options = {
-          port = mkOption {
-            type = types.port;
-            default = 3000;
-            description = ''
-              TCP port the process should listen to.
-            '';
-          };
-
-          bindIP = mkOption {
-            default = "0.0.0.0";
-            type = types.str;
-            description = ''
-              IPs the service should listen to.
-            '';
-          };
-
-          db = {
-            type = mkOption {
-              default = "postgres";
-              type = types.enum [ "postgres" "mysql" "mariadb" "mssql" ];
-              description = ''
-                Database driver to use for persistence. Please note that `sqlite`
-                is currently not supported as the build process for it is currently not implemented
-                in `pkgs.wiki-js` and it's not recommended by upstream for
-                production use.
-              '';
-            };
-            host = mkOption {
-              type = types.str;
-              example = "/run/postgresql";
-              description = ''
-                Hostname or socket-path to connect to.
-              '';
-            };
-            db = mkOption {
-              default = "wiki";
-              type = types.str;
-              description = ''
-                Name of the database to use.
-              '';
-            };
-          };
-
-          logLevel = mkOption {
-            default = "info";
-            type = types.enum [ "error" "warn" "info" "verbose" "debug" "silly" ];
-            description = ''
-              Define how much detail is supposed to be logged at runtime.
-            '';
-          };
-
-          offline = mkEnableOption "offline mode" // {
-            description = ''
-              Disable latest file updates and enable
-              [sideloading](https://docs.requarks.io/install/sideload).
-            '';
-          };
-        };
-      };
-      description = ''
-        Settings to configure `wiki-js`. This directly
-        corresponds to [the upstream configuration options](https://docs.requarks.io/install/config).
-
-        Secrets can be injected via the environment by
-        - specifying [](#opt-services.wiki-js.environmentFile)
-          to contain secrets
-        - and setting sensitive values to `$(ENVIRONMENT_VAR)`
-          with this value defined in the environment-file.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.wiki-js.settings.dataPath = "/var/lib/${cfg.stateDirectoryName}";
-    systemd.services.wiki-js = {
-      description = "A modern and powerful wiki app built on Node.js";
-      documentation = [ "https://docs.requarks.io/" ];
-      wantedBy = [ "multi-user.target" ];
-
-      path = with pkgs; [
-        # Needed for git storage.
-        git
-        # Needed for git+ssh storage.
-        openssh
-      ];
-
-      preStart = ''
-        ln -sf ${configFile} /var/lib/${cfg.stateDirectoryName}/config.yml
-        ln -sf ${pkgs.wiki-js}/server /var/lib/${cfg.stateDirectoryName}
-        ln -sf ${pkgs.wiki-js}/assets /var/lib/${cfg.stateDirectoryName}
-        ln -sf ${pkgs.wiki-js}/package.json /var/lib/${cfg.stateDirectoryName}/package.json
-      '';
-
-      serviceConfig = {
-        EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
-        StateDirectory = cfg.stateDirectoryName;
-        WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
-        DynamicUser = true;
-        PrivateTmp = true;
-        ExecStart = "${pkgs.nodejs_18}/bin/node ${pkgs.wiki-js}/server";
-      };
-    };
-  };
-
-  meta.maintainers = with maintainers; [ ma27 ];
-}
diff --git a/nixos/modules/services/web-apps/windmill.nix b/nixos/modules/services/web-apps/windmill.nix
deleted file mode 100644
index f5ec7f70e877..000000000000
--- a/nixos/modules/services/web-apps/windmill.nix
+++ /dev/null
@@ -1,177 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-  cfg = config.services.windmill;
-in
-{
-  options.services.windmill = {
-    enable = lib.mkEnableOption "windmill service";
-
-    serverPort = lib.mkOption {
-      type = lib.types.port;
-      default = 8001;
-      description = "Port the windmill server listens on.";
-    };
-
-    lspPort = lib.mkOption {
-      type = lib.types.port;
-      default = 3001;
-      description = "Port the windmill lsp listens on.";
-    };
-
-    database = {
-      name = lib.mkOption {
-        type = lib.types.str;
-        # the simplest database setup is to have the database named like the user.
-        default = "windmill";
-        description = "Database name.";
-      };
-
-      user = lib.mkOption {
-        type = lib.types.str;
-        # the simplest database setup is to have the database user like the name.
-        default = "windmill";
-        description = "Database user.";
-      };
-
-      urlPath = lib.mkOption {
-        type = lib.types.path;
-        description = ''
-          Path to the file containing the database url windmill should connect to. This is not deducted from database user and name as it might contain a secret
-        '';
-        example = "config.age.secrets.DATABASE_URL_FILE.path";
-      };
-      createLocally = lib.mkOption {
-        type = lib.types.bool;
-        default = true;
-        description = "Whether to create a local database automatically.";
-      };
-    };
-
-    baseUrl = lib.mkOption {
-      type = lib.types.str;
-      description = ''
-        The base url that windmill will be served on.
-      '';
-      example = "https://windmill.example.com";
-    };
-
-    logLevel = lib.mkOption {
-      type = lib.types.enum [ "error" "warn" "info" "debug" "trace" ];
-      default = "info";
-      description = "Log level";
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-
-    services.postgresql = lib.optionalAttrs (cfg.database.createLocally) {
-      enable = lib.mkDefault true;
-
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        { name = cfg.database.user;
-          ensureDBOwnership = true;
-        }
-      ];
-
-   };
-
-   systemd.services =
-    let
-      serviceConfig = {
-        DynamicUser = true;
-        # using the same user to simplify db connection
-        User = cfg.database.user;
-        ExecStart = "${pkgs.windmill}/bin/windmill";
-
-        Restart = "always";
-        LoadCredential = [
-          "DATABASE_URL_FILE:${cfg.database.urlPath}"
-        ];
-      };
-    in
-    {
-
-    # coming from https://github.com/windmill-labs/windmill/blob/main/init-db-as-superuser.sql
-    # modified to not grant priviledges on all tables
-    # create role windmill_user and windmill_admin only if they don't exist
-    postgresql.postStart = lib.mkIf cfg.database.createLocally (lib.mkAfter ''
-      $PSQL -tA <<"EOF"
-DO $$
-BEGIN
-    IF NOT EXISTS (
-        SELECT FROM pg_catalog.pg_roles
-        WHERE rolname = 'windmill_user'
-    ) THEN
-        CREATE ROLE windmill_user;
-        GRANT ALL PRIVILEGES ON DATABASE ${cfg.database.name} TO windmill_user;
-    ELSE
-      RAISE NOTICE 'Role "windmill_user" already exists. Skipping.';
-    END IF;
-    IF NOT EXISTS (
-        SELECT FROM pg_catalog.pg_roles
-        WHERE rolname = 'windmill_admin'
-    ) THEN
-      CREATE ROLE windmill_admin WITH BYPASSRLS;
-      GRANT windmill_user TO windmill_admin;
-    ELSE
-      RAISE NOTICE 'Role "windmill_admin" already exists. Skipping.';
-    END IF;
-    GRANT windmill_admin TO windmill;
-END
-$$;
-EOF
-    '');
-
-     windmill-server = {
-        description = "Windmill server";
-        after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
-        wantedBy = [ "multi-user.target" ];
-
-        serviceConfig = serviceConfig // { StateDirectory = "windmill";};
-
-        environment = {
-          DATABASE_URL_FILE = "%d/DATABASE_URL_FILE";
-          PORT = builtins.toString cfg.serverPort;
-          WM_BASE_URL = cfg.baseUrl;
-          RUST_LOG = cfg.logLevel;
-          MODE = "server";
-        };
-      };
-
-     windmill-worker = {
-        description = "Windmill worker";
-        after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
-        wantedBy = [ "multi-user.target" ];
-
-        serviceConfig = serviceConfig // { StateDirectory = "windmill-worker";};
-
-        environment = {
-          DATABASE_URL_FILE = "%d/DATABASE_URL_FILE";
-          WM_BASE_URL = cfg.baseUrl;
-          RUST_LOG = cfg.logLevel;
-          MODE = "worker";
-          WORKER_GROUP = "default";
-          KEEP_JOB_DIR = "false";
-        };
-      };
-
-     windmill-worker-native = {
-        description = "Windmill worker native";
-        after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
-        wantedBy = [ "multi-user.target" ];
-
-        serviceConfig = serviceConfig // { StateDirectory = "windmill-worker-native";};
-
-        environment = {
-          DATABASE_URL_FILE = "%d/DATABASE_URL_FILE";
-          WM_BASE_URL = cfg.baseUrl;
-          RUST_LOG = cfg.logLevel;
-          MODE = "worker";
-          WORKER_GROUP = "native";
-        };
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
deleted file mode 100644
index 0d49f2d92998..000000000000
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ /dev/null
@@ -1,568 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.services.wordpress;
-  eachSite = cfg.sites;
-  user = "wordpress";
-  webserver = config.services.${cfg.webserver};
-  stateDir = hostName: "/var/lib/wordpress/${hostName}";
-
-  pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
-    pname = "wordpress-${hostName}";
-    version = src.version;
-    src = cfg.package;
-
-    installPhase = ''
-      mkdir -p $out
-      cp -r * $out/
-
-      # symlink the wordpress config
-      ln -s ${wpConfig hostName cfg} $out/share/wordpress/wp-config.php
-      # symlink uploads directory
-      ln -s ${cfg.uploadsDir} $out/share/wordpress/wp-content/uploads
-      ln -s ${cfg.fontsDir} $out/share/wordpress/wp-content/fonts
-
-      # https://github.com/NixOS/nixpkgs/pull/53399
-      #
-      # Symlinking works for most plugins and themes, but Avada, for instance, fails to
-      # understand the symlink, causing its file path stripping to fail. This results in
-      # requests that look like: https://example.com/wp-content//nix/store/...plugin/path/some-file.js
-      # Since hard linking directories is not allowed, copying is the next best thing.
-
-      # copy additional plugin(s), theme(s) and language(s)
-      ${concatStringsSep "\n" (mapAttrsToList (name: theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${name}") cfg.themes)}
-      ${concatStringsSep "\n" (mapAttrsToList (name: plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${name}") cfg.plugins)}
-      ${concatMapStringsSep "\n" (language: "cp -r ${language} $out/share/wordpress/wp-content/languages/") cfg.languages}
-    '';
-  };
-
-  mergeConfig = cfg: {
-    # wordpress is installed onto a read-only file system
-    DISALLOW_FILE_EDIT = true;
-    AUTOMATIC_UPDATER_DISABLED = true;
-    DB_NAME = cfg.database.name;
-    DB_HOST = "${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}";
-    DB_USER = cfg.database.user;
-    DB_CHARSET = "utf8";
-    # Always set DB_PASSWORD even when passwordFile is not set. This is the
-    # default Wordpress behaviour.
-    DB_PASSWORD =  if (cfg.database.passwordFile != null) then { _file = cfg.database.passwordFile; } else "";
-  } // cfg.settings;
-
-  wpConfig = hostName: cfg: let
-    conf_gen = c: mapAttrsToList (k: v: "define('${k}', ${mkPhpValue v});") cfg.mergedConfig;
-  in pkgs.writeTextFile {
-    name = "wp-config-${hostName}.php";
-    text = ''
-      <?php
-        $table_prefix  = '${cfg.database.tablePrefix}';
-
-        require_once('${stateDir hostName}/secret-keys.php');
-
-        ${cfg.extraConfig}
-        ${concatStringsSep "\n" (conf_gen cfg.mergedConfig)}
-
-        if ( !defined('ABSPATH') )
-          define('ABSPATH', dirname(__FILE__) . '/');
-
-        require_once(ABSPATH . 'wp-settings.php');
-      ?>
-    '';
-    checkPhase = "${pkgs.php81}/bin/php --syntax-check $target";
-  };
-
-  mkPhpValue = v: let
-    isHasAttr = s: isAttrs v && hasAttr s v;
-  in
-    if isString v then escapeShellArg v
-    # NOTE: If any value contains a , (comma) this will not get escaped
-    else if isList v && any lib.strings.isCoercibleToString v then escapeShellArg (concatMapStringsSep "," toString v)
-    else if isInt v then toString v
-    else if isBool v then boolToString v
-    else if isHasAttr "_file" then "trim(file_get_contents(${lib.escapeShellArg v._file}))"
-    else if isHasAttr "_raw" then v._raw
-    else abort "The Wordpress config value ${lib.generators.toPretty {} v} can not be encoded."
-  ;
-
-  secretsVars = [ "AUTH_KEY" "SECURE_AUTH_KEY" "LOGGED_IN_KEY" "NONCE_KEY" "AUTH_SALT" "SECURE_AUTH_SALT" "LOGGED_IN_SALT" "NONCE_SALT" ];
-  secretsScript = hostStateDir: ''
-    # The match in this line is not a typo, see https://github.com/NixOS/nixpkgs/pull/124839
-    grep -q "LOOGGED_IN_KEY" "${hostStateDir}/secret-keys.php" && rm "${hostStateDir}/secret-keys.php"
-    if ! test -e "${hostStateDir}/secret-keys.php"; then
-      umask 0177
-      echo "<?php" >> "${hostStateDir}/secret-keys.php"
-      ${concatMapStringsSep "\n" (var: ''
-        echo "define('${var}', '`tr -dc a-zA-Z0-9 </dev/urandom | head -c 64`');" >> "${hostStateDir}/secret-keys.php"
-      '') secretsVars}
-      echo "?>" >> "${hostStateDir}/secret-keys.php"
-      chmod 440 "${hostStateDir}/secret-keys.php"
-    fi
-  '';
-
-  siteOpts = { lib, name, config, ... }:
-    {
-      options = {
-        package = mkPackageOption pkgs "wordpress" { };
-
-        uploadsDir = mkOption {
-          type = types.path;
-          default = "/var/lib/wordpress/${name}/uploads";
-          description = ''
-            This directory is used for uploads of pictures. The directory passed here is automatically
-            created and permissions adjusted as required.
-          '';
-        };
-
-        fontsDir = mkOption {
-          type = types.path;
-          default = "/var/lib/wordpress/${name}/fonts";
-          description = ''
-            This directory is used to download fonts from a remote location, e.g.
-            to host google fonts locally.
-          '';
-        };
-
-        plugins = mkOption {
-          type = with types; coercedTo
-            (listOf path)
-            (l: warn "setting this option with a list is deprecated"
-              listToAttrs (map (p: nameValuePair (p.name or (throw "${p} does not have a name")) p) l))
-            (attrsOf path);
-          default = {};
-          description = ''
-            Path(s) to respective plugin(s) which are copied from the 'plugins' directory.
-
-            ::: {.note}
-            These plugins need to be packaged before use, see example.
-            :::
-          '';
-          example = literalExpression ''
-            {
-              inherit (pkgs.wordpressPackages.plugins) embed-pdf-viewer-plugin;
-            }
-          '';
-        };
-
-        themes = mkOption {
-          type = with types; coercedTo
-            (listOf path)
-            (l: warn "setting this option with a list is deprecated"
-              listToAttrs (map (p: nameValuePair (p.name or (throw "${p} does not have a name")) p) l))
-            (attrsOf path);
-          default = { inherit (pkgs.wordpressPackages.themes) twentytwentythree; };
-          defaultText = literalExpression "{ inherit (pkgs.wordpressPackages.themes) twentytwentythree; }";
-          description = ''
-            Path(s) to respective theme(s) which are copied from the 'theme' directory.
-
-            ::: {.note}
-            These themes need to be packaged before use, see example.
-            :::
-          '';
-          example = literalExpression ''
-            {
-              inherit (pkgs.wordpressPackages.themes) responsive-theme;
-            }
-          '';
-        };
-
-        languages = mkOption {
-          type = types.listOf types.path;
-          default = [];
-          description = ''
-            List of path(s) to respective language(s) which are copied from the 'languages' directory.
-          '';
-          example = literalExpression ''
-            [
-              # Let's package the German language.
-              # For other languages try to replace language and country code in the download URL with your desired one.
-              # Reference https://translate.wordpress.org for available translations and
-              # codes.
-              (pkgs.stdenv.mkDerivation {
-                name = "language-de";
-                src = pkgs.fetchurl {
-                  url = "https://de.wordpress.org/wordpress-''${pkgs.wordpress.version}-de_DE.tar.gz";
-                  # Name is required to invalidate the hash when wordpress is updated
-                  name = "wordpress-''${pkgs.wordpress.version}-language-de";
-                  sha256 = "sha256-dlas0rXTSV4JAl8f/UyMbig57yURRYRhTMtJwF9g8h0=";
-                };
-                installPhase = "mkdir -p $out; cp -r ./wp-content/languages/* $out/";
-              })
-            ];
-          '';
-        };
-
-        database = {
-          host = mkOption {
-            type = types.str;
-            default = "localhost";
-            description = "Database host address.";
-          };
-
-          port = mkOption {
-            type = types.port;
-            default = 3306;
-            description = "Database host port.";
-          };
-
-          name = mkOption {
-            type = types.str;
-            default = "wordpress";
-            description = "Database name.";
-          };
-
-          user = mkOption {
-            type = types.str;
-            default = "wordpress";
-            description = "Database user.";
-          };
-
-          passwordFile = mkOption {
-            type = types.nullOr types.path;
-            default = null;
-            example = "/run/keys/wordpress-dbpassword";
-            description = ''
-              A file containing the password corresponding to
-              {option}`database.user`.
-            '';
-          };
-
-          tablePrefix = mkOption {
-            type = types.str;
-            default = "wp_";
-            description = ''
-              The $table_prefix is the value placed in the front of your database tables.
-              Change the value if you want to use something other than wp_ for your database
-              prefix. Typically this is changed if you are installing multiple WordPress blogs
-              in the same database.
-
-              See <https://codex.wordpress.org/Editing_wp-config.php#table_prefix>.
-            '';
-          };
-
-          socket = mkOption {
-            type = types.nullOr types.path;
-            default = null;
-            defaultText = literalExpression "/run/mysqld/mysqld.sock";
-            description = "Path to the unix socket file to use for authentication.";
-          };
-
-          createLocally = mkOption {
-            type = types.bool;
-            default = true;
-            description = "Create the database and database user locally.";
-          };
-        };
-
-        virtualHost = mkOption {
-          type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-          example = literalExpression ''
-            {
-              adminAddr = "webmaster@example.org";
-              forceSSL = true;
-              enableACME = true;
-            }
-          '';
-          description = ''
-            Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`.
-          '';
-        };
-
-        poolConfig = mkOption {
-          type = with types; attrsOf (oneOf [ str int bool ]);
-          default = {
-            "pm" = "dynamic";
-            "pm.max_children" = 32;
-            "pm.start_servers" = 2;
-            "pm.min_spare_servers" = 2;
-            "pm.max_spare_servers" = 4;
-            "pm.max_requests" = 500;
-          };
-          description = ''
-            Options for the WordPress PHP pool. See the documentation on `php-fpm.conf`
-            for details on configuration directives.
-          '';
-        };
-
-        settings = mkOption {
-          type = types.attrsOf types.anything;
-          default = {};
-          description = ''
-            Structural Wordpress configuration.
-            Refer to <https://developer.wordpress.org/apis/wp-config-php>
-            for details and supported values.
-          '';
-          example = literalExpression ''
-            {
-              WP_DEFAULT_THEME = "twentytwentytwo";
-              WP_SITEURL = "https://example.org";
-              WP_HOME = "https://example.org";
-              WP_DEBUG = true;
-              WP_DEBUG_DISPLAY = true;
-              WPLANG = "de_DE";
-              FORCE_SSL_ADMIN = true;
-              AUTOMATIC_UPDATER_DISABLED = true;
-            }
-          '';
-        };
-
-        mergedConfig = mkOption {
-          readOnly = true;
-          default = mergeConfig config;
-          defaultText = literalExpression ''
-            {
-              DISALLOW_FILE_EDIT = true;
-              AUTOMATIC_UPDATER_DISABLED = true;
-            }
-          '';
-          description = ''
-            Read only representation of the final configuration.
-          '';
-        };
-
-        extraConfig = mkOption {
-          type = types.lines;
-          default = "";
-          description = ''
-            Any additional text to be appended to the wp-config.php
-            configuration file. This is a PHP script. For configuration
-            settings, see <https://codex.wordpress.org/Editing_wp-config.php>.
-
-            **Note**: Please pass structured settings via
-            `services.wordpress.sites.${name}.settings` instead.
-          '';
-          example = ''
-            @ini_set( 'log_errors', 'Off' );
-            @ini_set( 'display_errors', 'On' );
-          '';
-        };
-
-      };
-
-      config.virtualHost.hostName = mkDefault name;
-    };
-in
-{
-  # interface
-  options = {
-    services.wordpress = {
-
-      sites = mkOption {
-        type = types.attrsOf (types.submodule siteOpts);
-        default = {};
-        description = "Specification of one or more WordPress sites to serve";
-      };
-
-      webserver = mkOption {
-        type = types.enum [ "httpd" "nginx" "caddy" ];
-        default = "httpd";
-        description = ''
-          Whether to use apache2 or nginx for virtual host management.
-
-          Further nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
-          See [](#opt-services.nginx.virtualHosts) for further information.
-
-          Further apache2 configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
-          See [](#opt-services.httpd.virtualHosts) for further information.
-        '';
-      };
-
-    };
-  };
-
-  # implementation
-  config = mkIf (eachSite != {}) (mkMerge [{
-
-    assertions =
-      (mapAttrsToList (hostName: cfg:
-        { assertion = cfg.database.createLocally -> cfg.database.user == user;
-          message = ''services.wordpress.sites."${hostName}".database.user must be ${user} if the database is to be automatically provisioned'';
-        }) eachSite) ++
-      (mapAttrsToList (hostName: cfg:
-        { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
-          message = ''services.wordpress.sites."${hostName}".database.passwordFile cannot be specified if services.wordpress.sites."${hostName}".database.createLocally is set to true.'';
-        }) eachSite);
-
-
-    services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = mapAttrsToList (hostName: cfg: cfg.database.name) eachSite;
-      ensureUsers = mapAttrsToList (hostName: cfg:
-        { name = cfg.database.user;
-          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
-        }
-      ) eachSite;
-    };
-
-    services.phpfpm.pools = mapAttrs' (hostName: cfg: (
-      nameValuePair "wordpress-${hostName}" {
-        inherit user;
-        group = webserver.group;
-        settings = {
-          "listen.owner" = webserver.user;
-          "listen.group" = webserver.group;
-        } // cfg.poolConfig;
-      }
-    )) eachSite;
-
-  }
-
-  (mkIf (cfg.webserver == "httpd") {
-    services.httpd = {
-      enable = true;
-      extraModules = [ "proxy_fcgi" ];
-      virtualHosts = mapAttrs (hostName: cfg: mkMerge [ cfg.virtualHost {
-        documentRoot = mkForce "${pkg hostName cfg}/share/wordpress";
-        extraConfig = ''
-          <Directory "${pkg hostName cfg}/share/wordpress">
-            <FilesMatch "\.php$">
-              <If "-f %{REQUEST_FILENAME}">
-                SetHandler "proxy:unix:${config.services.phpfpm.pools."wordpress-${hostName}".socket}|fcgi://localhost/"
-              </If>
-            </FilesMatch>
-
-            # standard wordpress .htaccess contents
-            <IfModule mod_rewrite.c>
-              RewriteEngine On
-              RewriteBase /
-              RewriteRule ^index\.php$ - [L]
-              RewriteCond %{REQUEST_FILENAME} !-f
-              RewriteCond %{REQUEST_FILENAME} !-d
-              RewriteRule . /index.php [L]
-            </IfModule>
-
-            DirectoryIndex index.php
-            Require all granted
-            Options +FollowSymLinks -Indexes
-          </Directory>
-
-          # https://wordpress.org/support/article/hardening-wordpress/#securing-wp-config-php
-          <Files wp-config.php>
-            Require all denied
-          </Files>
-        '';
-      } ]) eachSite;
-    };
-  })
-
-  {
-    systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
-      "d '${stateDir hostName}' 0750 ${user} ${webserver.group} - -"
-      "d '${cfg.uploadsDir}' 0750 ${user} ${webserver.group} - -"
-      "Z '${cfg.uploadsDir}' 0750 ${user} ${webserver.group} - -"
-      "d '${cfg.fontsDir}' 0750 ${user} ${webserver.group} - -"
-      "Z '${cfg.fontsDir}' 0750 ${user} ${webserver.group} - -"
-    ]) eachSite);
-
-    systemd.services = mkMerge [
-      (mapAttrs' (hostName: cfg: (
-        nameValuePair "wordpress-init-${hostName}" {
-          wantedBy = [ "multi-user.target" ];
-          before = [ "phpfpm-wordpress-${hostName}.service" ];
-          after = optional cfg.database.createLocally "mysql.service";
-          script = secretsScript (stateDir hostName);
-
-          serviceConfig = {
-            Type = "oneshot";
-            User = user;
-            Group = webserver.group;
-          };
-      })) eachSite)
-
-      (optionalAttrs (any (v: v.database.createLocally) (attrValues eachSite)) {
-        httpd.after = [ "mysql.service" ];
-      })
-    ];
-
-    users.users.${user} = {
-      group = webserver.group;
-      isSystemUser = true;
-    };
-  }
-
-  (mkIf (cfg.webserver == "nginx") {
-    services.nginx = {
-      enable = true;
-      virtualHosts = mapAttrs (hostName: cfg: {
-        serverName = mkDefault hostName;
-        root = "${pkg hostName cfg}/share/wordpress";
-        extraConfig = ''
-          index index.php;
-        '';
-        locations = {
-          "/" = {
-            priority = 200;
-            extraConfig = ''
-              try_files $uri $uri/ /index.php$is_args$args;
-            '';
-          };
-          "~ \\.php$" = {
-            priority = 500;
-            extraConfig = ''
-              fastcgi_split_path_info ^(.+\.php)(/.+)$;
-              fastcgi_pass unix:${config.services.phpfpm.pools."wordpress-${hostName}".socket};
-              fastcgi_index index.php;
-              include "${config.services.nginx.package}/conf/fastcgi.conf";
-              fastcgi_param PATH_INFO $fastcgi_path_info;
-              fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
-              # Mitigate https://httpoxy.org/ vulnerabilities
-              fastcgi_param HTTP_PROXY "";
-              fastcgi_intercept_errors off;
-              fastcgi_buffer_size 16k;
-              fastcgi_buffers 4 16k;
-              fastcgi_connect_timeout 300;
-              fastcgi_send_timeout 300;
-              fastcgi_read_timeout 300;
-            '';
-          };
-          "~ /\\." = {
-            priority = 800;
-            extraConfig = "deny all;";
-          };
-          "~* /(?:uploads|files)/.*\\.php$" = {
-            priority = 900;
-            extraConfig = "deny all;";
-          };
-          "~* \\.(js|css|png|jpg|jpeg|gif|ico)$" = {
-            priority = 1000;
-            extraConfig = ''
-              expires max;
-              log_not_found off;
-            '';
-          };
-        };
-      }) eachSite;
-    };
-  })
-
-  (mkIf (cfg.webserver == "caddy") {
-    services.caddy = {
-      enable = true;
-      virtualHosts = mapAttrs' (hostName: cfg: (
-        nameValuePair "http://${hostName}" {
-          extraConfig = ''
-            root    * /${pkg hostName cfg}/share/wordpress
-            file_server
-
-            php_fastcgi unix/${config.services.phpfpm.pools."wordpress-${hostName}".socket}
-
-            @uploads {
-              path_regexp path /uploads\/(.*)\.php
-            }
-            rewrite @uploads /
-
-            @wp-admin {
-              path  not ^\/wp-admin/*
-            }
-            rewrite @wp-admin {path}/index.php?{query}
-          '';
-        }
-      )) eachSite;
-    };
-  })
-
-
-  ]);
-}
diff --git a/nixos/modules/services/web-apps/writefreely.nix b/nixos/modules/services/web-apps/writefreely.nix
deleted file mode 100644
index 4bb5d8a579fd..000000000000
--- a/nixos/modules/services/web-apps/writefreely.nix
+++ /dev/null
@@ -1,482 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  inherit (builtins) toString;
-  inherit (lib) types mkIf mkOption mkDefault;
-  inherit (lib) optional optionals optionalAttrs optionalString;
-
-  inherit (pkgs) sqlite;
-
-  format = pkgs.formats.ini {
-    mkKeyValue = key: value:
-      let
-        value' = lib.optionalString (value != null)
-          (if builtins.isBool value then
-            if value == true then "true" else "false"
-          else
-            toString value);
-      in "${key} = ${value'}";
-  };
-
-  cfg = config.services.writefreely;
-
-  isSqlite = cfg.database.type == "sqlite3";
-  isMysql = cfg.database.type == "mysql";
-  isMysqlLocal = isMysql && cfg.database.createLocally == true;
-
-  hostProtocol = if cfg.acme.enable then "https" else "http";
-
-  settings = cfg.settings // {
-    app = cfg.settings.app or { } // {
-      host = cfg.settings.app.host or "${hostProtocol}://${cfg.host}";
-    };
-
-    database = if cfg.database.type == "sqlite3" then {
-      type = "sqlite3";
-      filename = cfg.settings.database.filename or "writefreely.db";
-      database = cfg.database.name;
-    } else {
-      type = "mysql";
-      username = cfg.database.user;
-      password = "#dbpass#";
-      database = cfg.database.name;
-      host = cfg.database.host;
-      port = cfg.database.port;
-      tls = cfg.database.tls;
-    };
-
-    server = cfg.settings.server or { } // {
-      bind = cfg.settings.server.bind or "localhost";
-      gopher_port = cfg.settings.server.gopher_port or 0;
-      autocert = !cfg.nginx.enable && cfg.acme.enable;
-      templates_parent_dir =
-        cfg.settings.server.templates_parent_dir or cfg.package.src;
-      static_parent_dir = cfg.settings.server.static_parent_dir or assets;
-      pages_parent_dir =
-        cfg.settings.server.pages_parent_dir or cfg.package.src;
-      keys_parent_dir = cfg.settings.server.keys_parent_dir or cfg.stateDir;
-    };
-  };
-
-  configFile = format.generate "config.ini" settings;
-
-  assets = pkgs.stdenvNoCC.mkDerivation {
-    pname = "writefreely-assets";
-
-    inherit (cfg.package) version src;
-
-    nativeBuildInputs = with pkgs.nodePackages; [ less ];
-
-    buildPhase = ''
-      mkdir -p $out
-
-      cp -r static $out/
-    '';
-
-    installPhase = ''
-      less_dir=$src/less
-      css_dir=$out/static/css
-
-      lessc $less_dir/app.less $css_dir/write.css
-      lessc $less_dir/fonts.less $css_dir/fonts.css
-      lessc $less_dir/icons.less $css_dir/icons.css
-      lessc $less_dir/prose.less $css_dir/prose.css
-    '';
-  };
-
-  withConfigFile = text: ''
-    db_pass=${
-      optionalString (cfg.database.passwordFile != null)
-      "$(head -n1 ${cfg.database.passwordFile})"
-    }
-
-    cp -f ${configFile} '${cfg.stateDir}/config.ini'
-    sed -e "s,#dbpass#,$db_pass,g" -i '${cfg.stateDir}/config.ini'
-    chmod 440 '${cfg.stateDir}/config.ini'
-
-    ${text}
-  '';
-
-  withMysql = text:
-    withConfigFile ''
-      query () {
-        local result=$(${config.services.mysql.package}/bin/mysql \
-          --user=${cfg.database.user} \
-          --password=$db_pass \
-          --database=${cfg.database.name} \
-          --silent \
-          --raw \
-          --skip-column-names \
-          --execute "$1" \
-        )
-
-        echo $result
-      }
-
-      ${text}
-    '';
-
-  withSqlite = text:
-    withConfigFile ''
-      query () {
-        local result=$(${sqlite}/bin/sqlite3 \
-          '${cfg.stateDir}/${settings.database.filename}' \
-          "$1" \
-        )
-
-        echo $result
-      }
-
-      ${text}
-    '';
-in {
-  options.services.writefreely = {
-    enable =
-      lib.mkEnableOption "Writefreely, build a digital writing community";
-
-    package = lib.mkOption {
-      type = lib.types.package;
-      default = pkgs.writefreely;
-      defaultText = lib.literalExpression "pkgs.writefreely";
-      description = "Writefreely package to use.";
-    };
-
-    stateDir = mkOption {
-      type = types.path;
-      default = "/var/lib/writefreely";
-      description = "The state directory where keys and data are stored.";
-    };
-
-    user = mkOption {
-      type = types.str;
-      default = "writefreely";
-      description = "User under which Writefreely is ran.";
-    };
-
-    group = mkOption {
-      type = types.str;
-      default = "writefreely";
-      description = "Group under which Writefreely is ran.";
-    };
-
-    host = mkOption {
-      type = types.str;
-      default = "";
-      description = "The public host name to serve.";
-      example = "example.com";
-    };
-
-    settings = mkOption {
-      default = { };
-      description = ''
-        Writefreely configuration ({file}`config.ini`). Refer to
-        <https://writefreely.org/docs/latest/admin/config>
-        for details.
-      '';
-
-      type = types.submodule {
-        freeformType = format.type;
-
-        options = {
-          app = {
-            theme = mkOption {
-              type = types.str;
-              default = "write";
-              description = "The theme to apply.";
-            };
-          };
-
-          server = {
-            port = mkOption {
-              type = types.port;
-              default = if cfg.nginx.enable then 18080 else 80;
-              defaultText = "80";
-              description = "The port WriteFreely should listen on.";
-            };
-          };
-        };
-      };
-    };
-
-    database = {
-      type = mkOption {
-        type = types.enum [ "sqlite3" "mysql" ];
-        default = "sqlite3";
-        description = "The database provider to use.";
-      };
-
-      name = mkOption {
-        type = types.str;
-        default = "writefreely";
-        description = "The name of the database to store data in.";
-      };
-
-      user = mkOption {
-        type = types.nullOr types.str;
-        default = if cfg.database.type == "mysql" then "writefreely" else null;
-        defaultText = "writefreely";
-        description = "The database user to connect as.";
-      };
-
-      passwordFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = "The file to load the database password from.";
-      };
-
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "The database host to connect to.";
-      };
-
-      port = mkOption {
-        type = types.port;
-        default = 3306;
-        description = "The port used when connecting to the database host.";
-      };
-
-      tls = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether or not TLS should be used for the database connection.";
-      };
-
-      migrate = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Whether or not to automatically run migrations on startup.";
-      };
-
-      createLocally = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          When {option}`services.writefreely.database.type` is set to
-          `"mysql"`, this option will enable the MySQL service locally.
-        '';
-      };
-    };
-
-    admin = {
-      name = mkOption {
-        type = types.nullOr types.str;
-        description = "The name of the first admin user.";
-        default = null;
-      };
-
-      initialPasswordFile = mkOption {
-        type = types.path;
-        description = ''
-          Path to a file containing the initial password for the admin user.
-          If not provided, the default password will be set to `nixos`.
-        '';
-        default = pkgs.writeText "default-admin-pass" "nixos";
-        defaultText = "/nix/store/xxx-default-admin-pass";
-      };
-    };
-
-    nginx = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether or not to enable and configure nginx as a proxy for WriteFreely.";
-      };
-
-      forceSSL = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether or not to force the use of SSL.";
-      };
-    };
-
-    acme = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether or not to automatically fetch and configure SSL certs.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = cfg.host != "";
-        message = "services.writefreely.host must be set";
-      }
-      {
-        assertion = isMysqlLocal -> cfg.database.passwordFile != null;
-        message =
-          "services.writefreely.database.passwordFile must be set if services.writefreely.database.createLocally is set to true";
-      }
-      {
-        assertion = isSqlite -> !cfg.database.createLocally;
-        message =
-          "services.writefreely.database.createLocally has no use when services.writefreely.database.type is set to sqlite3";
-      }
-    ];
-
-    users = {
-      users = optionalAttrs (cfg.user == "writefreely") {
-        writefreely = {
-          group = cfg.group;
-          home = cfg.stateDir;
-          isSystemUser = true;
-        };
-      };
-
-      groups =
-        optionalAttrs (cfg.group == "writefreely") { writefreely = { }; };
-    };
-
-    systemd.tmpfiles.settings."10-writefreely".${cfg.stateDir}.d = {
-      inherit (cfg) user group;
-      mode = "0750";
-    };
-
-    systemd.services.writefreely = {
-      after = [ "network.target" ]
-        ++ optional isSqlite "writefreely-sqlite-init.service"
-        ++ optional isMysql "writefreely-mysql-init.service"
-        ++ optional isMysqlLocal "mysql.service";
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        Type = "simple";
-        User = cfg.user;
-        Group = cfg.group;
-        WorkingDirectory = cfg.stateDir;
-        Restart = "always";
-        RestartSec = 20;
-        ExecStart =
-          "${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' serve";
-        AmbientCapabilities =
-          optionalString (settings.server.port < 1024) "cap_net_bind_service";
-      };
-
-      preStart = ''
-        if ! test -d "${cfg.stateDir}/keys"; then
-          mkdir -p ${cfg.stateDir}/keys
-
-          # Key files end up with the wrong permissions by default.
-          # We need to correct them so that Writefreely can read them.
-          chmod -R 750 "${cfg.stateDir}/keys"
-
-          ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' keys generate
-        fi
-      '';
-    };
-
-    systemd.services.writefreely-sqlite-init = mkIf isSqlite {
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        Type = "oneshot";
-        User = cfg.user;
-        Group = cfg.group;
-        WorkingDirectory = cfg.stateDir;
-        ReadOnlyPaths = optional (cfg.admin.initialPasswordFile != null)
-          cfg.admin.initialPasswordFile;
-      };
-
-      script = let
-        migrateDatabase = optionalString cfg.database.migrate ''
-          ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate
-        '';
-
-        createAdmin = optionalString (cfg.admin.name != null) ''
-          if [[ $(query "SELECT COUNT(*) FROM users") == 0 ]]; then
-            admin_pass=$(head -n1 ${cfg.admin.initialPasswordFile})
-
-            ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass
-          fi
-        '';
-      in withSqlite ''
-        if ! test -f '${settings.database.filename}'; then
-          ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init
-        fi
-
-        ${migrateDatabase}
-
-        ${createAdmin}
-      '';
-    };
-
-    systemd.services.writefreely-mysql-init = mkIf isMysql {
-      wantedBy = [ "multi-user.target" ];
-      after = optional isMysqlLocal "mysql.service";
-
-      serviceConfig = {
-        Type = "oneshot";
-        User = cfg.user;
-        Group = cfg.group;
-        WorkingDirectory = cfg.stateDir;
-        ReadOnlyPaths = optional isMysqlLocal cfg.database.passwordFile
-          ++ optional (cfg.admin.initialPasswordFile != null)
-          cfg.admin.initialPasswordFile;
-      };
-
-      script = let
-        updateUser = optionalString isMysqlLocal ''
-          # WriteFreely currently *requires* a password for authentication, so we
-          # need to update the user in MySQL accordingly. By default MySQL users
-          # authenticate with auth_socket or unix_socket.
-          # See: https://github.com/writefreely/writefreely/issues/568
-          ${config.services.mysql.package}/bin/mysql --skip-column-names --execute "ALTER USER '${cfg.database.user}'@'localhost' IDENTIFIED VIA unix_socket OR mysql_native_password USING PASSWORD('$db_pass'); FLUSH PRIVILEGES;"
-        '';
-
-        migrateDatabase = optionalString cfg.database.migrate ''
-          ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate
-        '';
-
-        createAdmin = optionalString (cfg.admin.name != null) ''
-          if [[ $(query 'SELECT COUNT(*) FROM users') == 0 ]]; then
-            admin_pass=$(head -n1 ${cfg.admin.initialPasswordFile})
-            ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass
-          fi
-        '';
-      in withMysql ''
-        ${updateUser}
-
-        if [[ $(query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${cfg.database.name}'") == 0 ]]; then
-          ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init
-        fi
-
-        ${migrateDatabase}
-
-        ${createAdmin}
-      '';
-    };
-
-    services.mysql = mkIf isMysqlLocal {
-      enable = true;
-      package = mkDefault pkgs.mariadb;
-      ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [{
-        name = cfg.database.user;
-        ensurePermissions = {
-          "${cfg.database.name}.*" = "ALL PRIVILEGES";
-          # WriteFreely requires the use of passwords, so we need permissions
-          # to `ALTER` the user to add password support and also to reload
-          # permissions so they can be used.
-          "*.*" = "CREATE USER, RELOAD";
-        };
-      }];
-    };
-
-    services.nginx = lib.mkIf cfg.nginx.enable {
-      enable = true;
-      recommendedProxySettings = true;
-
-      virtualHosts."${cfg.host}" = {
-        enableACME = cfg.acme.enable;
-        forceSSL = cfg.nginx.forceSSL;
-
-        locations."/" = {
-          proxyPass = "http://127.0.0.1:${toString settings.server.port}";
-        };
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/your_spotify.nix b/nixos/modules/services/web-apps/your_spotify.nix
deleted file mode 100644
index 3eb2ffef4f93..000000000000
--- a/nixos/modules/services/web-apps/your_spotify.nix
+++ /dev/null
@@ -1,191 +0,0 @@
-{
-  pkgs,
-  config,
-  lib,
-  ...
-}: let
-  inherit
-    (lib)
-    boolToString
-    concatMapAttrs
-    concatStrings
-    isBool
-    mapAttrsToList
-    mkEnableOption
-    mkIf
-    mkOption
-    mkPackageOption
-    optionalAttrs
-    types
-    mkDefault
-    ;
-  cfg = config.services.your_spotify;
-
-  configEnv = concatMapAttrs (name: value:
-    optionalAttrs (value != null) {
-      ${name} =
-        if isBool value
-        then boolToString value
-        else toString value;
-    })
-  cfg.settings;
-
-  configFile = pkgs.writeText "your_spotify.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv));
-in {
-  options.services.your_spotify = let
-    inherit (types) nullOr port str path package;
-  in {
-    enable = mkEnableOption "your_spotify";
-
-    enableLocalDB = mkEnableOption "a local mongodb instance";
-    nginxVirtualHost = mkOption {
-      type = nullOr str;
-      default = null;
-      description = ''
-        If set creates an nginx virtual host for the client.
-        In most cases this should be the CLIENT_ENDPOINT without
-        protocol prefix.
-      '';
-    };
-
-    package = mkPackageOption pkgs "your_spotify" {};
-
-    clientPackage = mkOption {
-      type = package;
-      description = "Client package to use.";
-    };
-
-    spotifySecretFile = mkOption {
-      type = path;
-      description = ''
-        A file containing the secret key of your Spotify application.
-        Refer to: [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application).
-      '';
-    };
-
-    settings = mkOption {
-      description = ''
-        Your Spotify Configuration. Refer to [Your Spotify](https://github.com/Yooooomi/your_spotify) for definitions and values.
-      '';
-      example = lib.literalExpression ''
-        {
-          CLIENT_ENDPOINT = "https://example.com";
-          API_ENDPOINT = "https://api.example.com";
-          SPOTIFY_PUBLIC = "spotify_client_id";
-        }
-      '';
-      type = types.submodule {
-        freeformType = types.attrsOf types.str;
-        options = {
-          CLIENT_ENDPOINT = mkOption {
-            type = str;
-            description = ''
-              The endpoint of your web application.
-              Has to include a protocol Prefix (e.g. `http://`)
-            '';
-            example = "https://your_spotify.example.org";
-          };
-          API_ENDPOINT = mkOption {
-            type = str;
-            description = ''
-              The endpoint of your server
-              This api has to be reachable from the device you use the website from not from the server.
-              This means that for example you may need two nginx virtual hosts if you want to expose this on the
-              internet.
-              Has to include a protocol Prefix (e.g. `http://`)
-            '';
-            example = "https://localhost:3000";
-          };
-          SPOTIFY_PUBLIC = mkOption {
-            type = str;
-            description = ''
-              The public client ID of your Spotify application.
-              Refer to: [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application)
-            '';
-          };
-          MONGO_ENDPOINT = mkOption {
-            type = str;
-            description = ''The endpoint of the Mongo database.'';
-            default = "mongodb://localhost:27017/your_spotify";
-          };
-          PORT = mkOption {
-            type = port;
-            description = "The port of the api server";
-            default = 3000;
-          };
-        };
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.your_spotify.clientPackage = mkDefault (cfg.package.client.override {apiEndpoint = cfg.settings.API_ENDPOINT;});
-    systemd.services.your_spotify = {
-      after = ["network.target"];
-      script = ''
-        export SPOTIFY_SECRET=$(< "$CREDENTIALS_DIRECTORY/SPOTIFY_SECRET")
-        ${lib.getExe' cfg.package "your_spotify_migrate"}
-        exec ${lib.getExe cfg.package}
-      '';
-      serviceConfig = {
-        User = "your_spotify";
-        Group = "your_spotify";
-        DynamicUser = true;
-        EnvironmentFile = [configFile];
-        StateDirectory = "your_spotify";
-        LimitNOFILE = "1048576";
-        PrivateTmp = true;
-        PrivateDevices = true;
-        StateDirectoryMode = "0700";
-        Restart = "always";
-
-        LoadCredential = ["SPOTIFY_SECRET:${cfg.spotifySecretFile}"];
-
-        # Hardening
-        CapabilityBoundingSet = "";
-        LockPersonality = true;
-        #MemoryDenyWriteExecute = true; # Leads to coredump because V8 does JIT
-        PrivateUsers = true;
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectProc = "invisible";
-        ProcSubset = "pid";
-        ProtectSystem = "strict";
-        RestrictAddressFamilies = [
-          "AF_INET"
-          "AF_INET6"
-          "AF_NETLINK"
-        ];
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [
-          "@system-service"
-          "@pkey"
-        ];
-        UMask = "0077";
-      };
-      wantedBy = ["multi-user.target"];
-    };
-    services.nginx = mkIf (cfg.nginxVirtualHost != null) {
-      enable = true;
-      virtualHosts.${cfg.nginxVirtualHost} = {
-        root = cfg.clientPackage;
-        locations."/".extraConfig = ''
-          add_header Content-Security-Policy "frame-ancestors 'none';" ;
-          add_header X-Content-Type-Options "nosniff" ;
-          try_files = $uri $uri/ /index.html ;
-        '';
-      };
-    };
-    services.mongodb = mkIf cfg.enableLocalDB {
-      enable = true;
-    };
-  };
-  meta.maintainers = with lib.maintainers; [patrickdag];
-}
diff --git a/nixos/modules/services/web-apps/youtrack.md b/nixos/modules/services/web-apps/youtrack.md
deleted file mode 100644
index f33f482ff970..000000000000
--- a/nixos/modules/services/web-apps/youtrack.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# YouTrack {#module-services-youtrack}
-
-YouTrack is a browser-based bug tracker, issue tracking system and project management software.
-
-## Installation {#module-services-youtrack-installation}
-
-YouTrack exposes a web GUI installer on first login.
-You need a token to access it.
-You can find this token in the log of the `youtrack` service. The log line looks like
-```
-* JetBrains YouTrack 2023.3 Configuration Wizard will be available on [http://127.0.0.1:8090/?wizard_token=somelongtoken] after start
-```
-
-## Upgrade from 2022.3 to 2023.x {#module-services-youtrack-upgrade-2022_3-2023_1}
-
-Starting with YouTrack 2023.1, JetBrains no longer distributes it as as JAR.
-The new distribution with the JetBrains Launcher as a ZIP changed the basic data structure and also some configuration parameters.
-Check out https://www.jetbrains.com/help/youtrack/server/YouTrack-Java-Start-Parameters.html for more information on the new configuration options.
-When upgrading to YouTrack 2023.1 or higher, a migration script will move the old state directory to `/var/lib/youtrack/2022_3` as a backup.
-A one-time manual update is required:
-
-1. Before you update take a backup of your YouTrack instance!
-2. Migrate the options you set in `services.youtrack.extraParams` and `services.youtrack.jvmOpts` to `services.youtrack.generalParameters` and `services.youtrack.environmentalParameters` (see the examples and [the YouTrack docs](https://www.jetbrains.com/help/youtrack/server/2023.3/YouTrack-Java-Start-Parameters.html))
-2. To start the upgrade set `services.youtrack.package = pkgs.youtrack`
-3. YouTrack then starts in upgrade mode, meaning you need to obtain the wizard token as above
-4. Select you want to **Upgrade** YouTrack
-5. As source you select `/var/lib/youtrack/2022_3/teamsysdata/` (adopt if you have a different state path)
-6. Change the data directory location to `/var/lib/youtrack/data/`. The other paths should already be right.
-
-If you migrate a larger YouTrack instance, it might be useful to set `-Dexodus.entityStore.refactoring.forceAll=true` in `services.youtrack.generalParameters` for the first startup of YouTrack 2023.x.
diff --git a/nixos/modules/services/web-apps/youtrack.nix b/nixos/modules/services/web-apps/youtrack.nix
deleted file mode 100644
index ff48a978b734..000000000000
--- a/nixos/modules/services/web-apps/youtrack.nix
+++ /dev/null
@@ -1,269 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.youtrack;
-in
-{
-  imports = [
-    (lib.mkRenamedOptionModule [ "services" "youtrack" "baseUrl" ] [ "services" "youtrack" "environmentalParameters" "base-url" ])
-    (lib.mkRenamedOptionModule [ "services" "youtrack" "port" ] [ "services" "youtrack" "environmentalParameters" "listen-port" ])
-    (lib.mkRemovedOptionModule [ "services" "youtrack" "maxMemory" ] "Please instead use `services.youtrack.generalParameters`.")
-    (lib.mkRemovedOptionModule [ "services" "youtrack" "maxMetaspaceSize" ] "Please instead use `services.youtrack.generalParameters`.")
-  ];
-
-  options.services.youtrack = {
-    enable = lib.mkEnableOption "YouTrack service";
-
-    address = lib.mkOption {
-      description = ''
-        The interface youtrack will listen on.
-      '';
-      default = "127.0.0.1";
-      type = lib.types.str;
-    };
-
-    extraParams = lib.mkOption {
-      default = {};
-      description = ''
-        Extra parameters to pass to youtrack.
-        Use to configure YouTrack 2022.x, deprecated with YouTrack 2023.x. Use `services.youtrack.generalParameters`.
-        https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Java-Start-Parameters.html
-        for more information.
-      '';
-      example = lib.literalExpression ''
-        {
-          "jetbrains.youtrack.overrideRootPassword" = "tortuga";
-        }
-      '';
-      type = lib.types.attrsOf lib.types.str;
-      visible = false;
-    };
-
-    package = lib.mkOption {
-      description = ''
-        Package to use.
-      '';
-      type = lib.types.package;
-      default = null;
-      relatedPackages = [ "youtrack_2022_3" "youtrack" ];
-    };
-
-
-    statePath = lib.mkOption {
-      description = ''
-        Path were the YouTrack state is stored.
-        To this path the base version (e.g. 2023_1) of the used package will be appended.
-      '';
-      type = lib.types.path;
-      default = "/var/lib/youtrack";
-    };
-
-    virtualHost = lib.mkOption {
-      description = ''
-        Name of the nginx virtual host to use and setup.
-        If null, do not setup anything.
-      '';
-      default = null;
-      type = lib.types.nullOr lib.types.str;
-    };
-
-    jvmOpts = lib.mkOption {
-      description = ''
-        Extra options to pass to the JVM.
-        Only has a use with YouTrack 2022.x, deprecated with YouTrack 2023.x. Use `serivces.youtrack.generalParameters`.
-        See https://www.jetbrains.com/help/youtrack/standalone/Configure-JVM-Options.html
-        for more information.
-      '';
-      type = lib.types.separatedString " ";
-      example = "--J-XX:MetaspaceSize=250m";
-      default = "";
-      visible = false;
-    };
-
-    autoUpgrade = lib.mkOption {
-      type = lib.types.bool;
-      default = true;
-      description = "Whether YouTrack should auto upgrade it without showing the upgrade dialog.";
-    };
-
-    generalParameters = lib.mkOption {
-      type = with lib.types; listOf str;
-      description = ''
-        General configuration parameters and other JVM options.
-        Only has an effect for YouTrack 2023.x.
-        See https://www.jetbrains.com/help/youtrack/server/2023.3/youtrack-java-start-parameters.html#general-parameters
-        for more information.
-      '';
-      example = lib.literalExpression ''
-        [
-          "-Djetbrains.youtrack.admin.restore=true"
-          "-Xmx1024m"
-        ];
-      '';
-      default = [];
-    };
-
-    environmentalParameters = lib.mkOption {
-      type = lib.types.submodule {
-        freeformType = with lib.types; attrsOf (oneOf [ int str port ]);
-        options = {
-          listen-address = lib.mkOption {
-            type = lib.types.str;
-            default = "0.0.0.0";
-            description = "The interface YouTrack will listen on.";
-          };
-          listen-port = lib.mkOption {
-            type = lib.types.port;
-            default = 8080;
-            description = "The port YouTrack will listen on.";
-          };
-        };
-      };
-      description = ''
-        Environmental configuration parameters, set imperatively. The values doesn't get removed, when removed in Nix.
-        Only has an effect for YouTrack 2023.x.
-        See https://www.jetbrains.com/help/youtrack/server/2023.3/youtrack-java-start-parameters.html#environmental-parameters
-        for more information.
-      '';
-      example = lib.literalExpression ''
-        {
-          secure-mode = "tls";
-        }
-      '';
-      default = {};
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    warnings = lib.optional (lib.versions.major cfg.package.version <= "2022")
-      "YouTrack 2022.x is deprecated. See https://nixos.org/manual/nixos/unstable/index.html#module-services-youtrack for details on how to upgrade."
-    ++ lib.optional (cfg.extraParams != {} && (lib.versions.major cfg.package.version >= "2023"))
-      "'services.youtrack.extraParams' is deprecated and has no effect on YouTrack 2023.x and newer. Please migrate to 'services.youtrack.generalParameters'"
-    ++ lib.optional (cfg.jvmOpts != "" && (lib.versions.major cfg.package.version >= "2023"))
-      "'services.youtrack.jvmOpts' is deprecated and has no effect on YouTrack 2023.x and newer. Please migrate to 'services.youtrack.generalParameters'";
-
-    # XXX: Drop all version feature switches at the point when we consider YT 2022.3 as outdated.
-    services.youtrack.package = lib.mkDefault (
-      if lib.versionAtLeast config.system.stateVersion "24.11" then pkgs.youtrack
-      else pkgs.youtrack_2022_3
-    );
-
-    services.youtrack.generalParameters = lib.optional (lib.versions.major cfg.package.version >= "2023")
-      "-Ddisable.configuration.wizard.on.upgrade=${lib.boolToString cfg.autoUpgrade}"
-      ++ (lib.mapAttrsToList (k: v: "-D${k}=${v}") cfg.extraParams);
-
-    systemd.services.youtrack = let
-      service_jar = let
-        mergeAttrList = lib.foldl' lib.mergeAttrs {};
-        stdParams = mergeAttrList [
-          (lib.optionalAttrs (cfg.environmentalParameters ? base-url && cfg.environmentalParameters.base-url != null) {
-            "jetbrains.youtrack.baseUrl" = cfg.environmentalParameters.base-url;
-          })
-          {
-          "java.aws.headless" = "true";
-          "jetbrains.youtrack.disableBrowser" = "true";
-          }
-        ];
-        extraAttr = lib.concatStringsSep " " (lib.mapAttrsToList (k: v: "-D${k}=${v}") (stdParams // cfg.extraParams));
-      in {
-        environment.HOME = cfg.statePath;
-        environment.YOUTRACK_JVM_OPTS = "${extraAttr}";
-        after = [ "network.target" ];
-        wantedBy = [ "multi-user.target" ];
-        path = with pkgs; [ unixtools.hostname ];
-        serviceConfig = {
-          Type = "simple";
-          User = "youtrack";
-          Group = "youtrack";
-          Restart = "on-failure";
-          ExecStart = ''${cfg.package}/bin/youtrack ${cfg.jvmOpts} ${cfg.environmentalParameters.listen-address}:${toString cfg.environmentalParameters.listen-port}'';
-        };
-      };
-      service_zip = let
-        jvmoptions = pkgs.writeTextFile {
-          name = "youtrack.jvmoptions";
-          text = (lib.concatStringsSep "\n" cfg.generalParameters);
-        };
-
-        package = cfg.package.override {
-          statePath = cfg.statePath;
-        };
-      in {
-        after = [ "network.target" ];
-        wantedBy = [ "multi-user.target" ];
-        path = with pkgs; [ unixtools.hostname ];
-        preStart = ''
-          # This detects old (i.e. <= 2022.3) installations that were not migrated yet
-          # and migrates them to the new state directory style
-          if [[ -d ${cfg.statePath}/teamsysdata ]] && [[ ! -d ${cfg.statePath}/2022_3 ]]
-          then
-            mkdir -p ${cfg.statePath}/2022_3
-            mv ${cfg.statePath}/teamsysdata ${cfg.statePath}/2022_3
-            mv ${cfg.statePath}/.youtrack ${cfg.statePath}/2022_3
-          fi
-          mkdir -p ${cfg.statePath}/{backups,conf,data,logs,temp}
-          ${pkgs.coreutils}/bin/ln -fs ${jvmoptions} ${cfg.statePath}/conf/youtrack.jvmoptions
-          ${package}/bin/youtrack configure ${lib.concatStringsSep " " (lib.mapAttrsToList (name: value: "--${name}=${toString value}") cfg.environmentalParameters )}
-        '';
-        serviceConfig = lib.mkMerge [
-          {
-            Type = "simple";
-            User = "youtrack";
-            Group = "youtrack";
-            Restart = "on-failure";
-            ExecStart = "${package}/bin/youtrack run";
-          }
-          (lib.mkIf (cfg.statePath == "/var/lib/youtrack") {
-            StateDirectory = "youtrack";
-          })
-        ];
-      };
-    in if (lib.versions.major cfg.package.version >= "2023") then service_zip else service_jar;
-
-    users.users.youtrack = {
-      description = "Youtrack service user";
-      isSystemUser = true;
-      home = cfg.statePath;
-      createHome = true;
-      group = "youtrack";
-    };
-
-    users.groups.youtrack = {};
-
-    services.nginx = lib.mkIf (cfg.virtualHost != null) {
-      upstreams.youtrack.servers."${cfg.address}:${toString cfg.environmentalParameters.listen-port}" = {};
-      virtualHosts.${cfg.virtualHost}.locations = {
-        "/" = {
-          proxyPass = "http://youtrack";
-          extraConfig = ''
-            client_max_body_size 10m;
-            proxy_http_version 1.1;
-            proxy_set_header X-Forwarded-Host $http_host;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-            proxy_set_header X-Forwarded-Proto $scheme;
-          '';
-        };
-
-        "/api/eventSourceBus" = {
-          proxyPass = "http://youtrack";
-          extraConfig = ''
-            proxy_cache off;
-            proxy_buffering off;
-            proxy_read_timeout 86400s;
-            proxy_send_timeout 86400s;
-            proxy_set_header Connection "";
-            chunked_transfer_encoding off;
-            client_max_body_size 10m;
-            proxy_http_version 1.1;
-            proxy_set_header X-Forwarded-Host $http_host;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-            proxy_set_header X-Forwarded-Proto $scheme;
-          '';
-        };
-      };
-    };
-  };
-
-  meta.doc = ./youtrack.md;
-  meta.maintainers = [ lib.maintainers.leona ];
-}
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
deleted file mode 100644
index 2455e676e583..000000000000
--- a/nixos/modules/services/web-apps/zabbix.nix
+++ /dev/null
@@ -1,233 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-let
-
-  inherit (lib) mkDefault mkEnableOption mkPackageOption mkForce mkIf mkMerge mkOption types;
-  inherit (lib) literalExpression mapAttrs optionalString versionAtLeast;
-
-  cfg = config.services.zabbixWeb;
-  opt = options.services.zabbixWeb;
-  fpm = config.services.phpfpm.pools.zabbix;
-
-  user = "zabbix";
-  group = "zabbix";
-  stateDir = "/var/lib/zabbix";
-
-  zabbixConfig = pkgs.writeText "zabbix.conf.php" ''
-    <?php
-    // Zabbix GUI configuration file.
-    global $DB;
-    $DB['TYPE'] = '${ { mysql = "MYSQL"; pgsql = "POSTGRESQL"; oracle = "ORACLE"; }.${cfg.database.type} }';
-    $DB['SERVER'] = '${cfg.database.host}';
-    $DB['PORT'] = '${toString cfg.database.port}';
-    $DB['DATABASE'] = '${cfg.database.name}';
-    $DB['USER'] = '${cfg.database.user}';
-    # NOTE: file_get_contents adds newline at the end of returned string
-    $DB['PASSWORD'] = ${if cfg.database.passwordFile != null then "trim(file_get_contents('${cfg.database.passwordFile}'), \"\\r\\n\")" else "''"};
-    // Schema name. Used for IBM DB2 and PostgreSQL.
-    $DB['SCHEMA'] = ''';
-    $ZBX_SERVER = '${cfg.server.address}';
-    $ZBX_SERVER_PORT = '${toString cfg.server.port}';
-    $ZBX_SERVER_NAME = ''';
-    $IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
-
-    ${cfg.extraConfig}
-  '';
-
-in
-{
-  # interface
-
-  options.services = {
-    zabbixWeb = {
-      enable = mkEnableOption "the Zabbix web interface";
-
-      package = mkPackageOption pkgs [ "zabbix" "web" ] { };
-
-      server = {
-        port = mkOption {
-          type = types.port;
-          description = "The port of the Zabbix server to connect to.";
-          default = 10051;
-        };
-
-        address = mkOption {
-          type = types.str;
-          description = "The IP address or hostname of the Zabbix server to connect to.";
-          default = "localhost";
-        };
-      };
-
-      database = {
-        type = mkOption {
-          type = types.enum [ "mysql" "pgsql" "oracle" ];
-          example = "mysql";
-          default = "pgsql";
-          description = "Database engine to use.";
-        };
-
-        host = mkOption {
-          type = types.str;
-          default = "";
-          description = "Database host address.";
-        };
-
-        port = mkOption {
-          type = types.port;
-          default =
-            if cfg.database.type == "mysql" then config.services.mysql.port
-            else if cfg.database.type == "pgsql" then config.services.postgresql.settings.port
-            else 1521;
-          defaultText = literalExpression ''
-            if config.${opt.database.type} == "mysql" then config.${options.services.mysql.port}
-            else if config.${opt.database.type} == "pgsql" then config.services.postgresql.settings.port
-            else 1521
-          '';
-          description = "Database host port.";
-        };
-
-        name = mkOption {
-          type = types.str;
-          default = "zabbix";
-          description = "Database name.";
-        };
-
-        user = mkOption {
-          type = types.str;
-          default = "zabbix";
-          description = "Database user.";
-        };
-
-        passwordFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          example = "/run/keys/zabbix-dbpassword";
-          description = ''
-            A file containing the password corresponding to
-            {option}`database.user`.
-          '';
-        };
-
-        socket = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          example = "/run/postgresql";
-          description = "Path to the unix socket file to use for authentication.";
-        };
-      };
-
-      virtualHost = mkOption {
-        type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-        example = literalExpression ''
-          {
-            hostName = "zabbix.example.org";
-            adminAddr = "webmaster@example.org";
-            forceSSL = true;
-            enableACME = true;
-          }
-        '';
-        description = ''
-          Apache configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
-          See [](#opt-services.httpd.virtualHosts) for further information.
-        '';
-      };
-
-      poolConfig = mkOption {
-        type = with types; attrsOf (oneOf [ str int bool ]);
-        default = {
-          "pm" = "dynamic";
-          "pm.max_children" = 32;
-          "pm.start_servers" = 2;
-          "pm.min_spare_servers" = 2;
-          "pm.max_spare_servers" = 4;
-          "pm.max_requests" = 500;
-        };
-        description = ''
-          Options for the Zabbix PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = ''
-          Additional configuration to be copied verbatim into {file}`zabbix.conf.php`.
-        '';
-      };
-
-    };
-  };
-
-  # implementation
-
-  config = mkIf cfg.enable {
-
-    services.zabbixWeb.extraConfig = optionalString ((versionAtLeast config.system.stateVersion "20.09") && (versionAtLeast cfg.package.version "5.0.0")) ''
-      $DB['DOUBLE_IEEE754'] = 'true';
-    '';
-
-    systemd.tmpfiles.rules = [
-      "d '${stateDir}' 0750 ${user} ${group} - -"
-      "d '${stateDir}/session' 0750 ${user} ${config.services.httpd.group} - -"
-    ];
-
-    services.phpfpm.pools.zabbix = {
-      inherit user;
-      group = config.services.httpd.group;
-      phpOptions = ''
-        # https://www.zabbix.com/documentation/current/manual/installation/install
-        memory_limit = 128M
-        post_max_size = 16M
-        upload_max_filesize = 2M
-        max_execution_time = 300
-        max_input_time = 300
-        session.auto_start = 0
-        mbstring.func_overload = 0
-        always_populate_raw_post_data = -1
-        # https://bbs.archlinux.org/viewtopic.php?pid=1745214#p1745214
-        session.save_path = ${stateDir}/session
-      '' + optionalString (config.time.timeZone != null) ''
-        date.timezone = "${config.time.timeZone}"
-      '' + optionalString (cfg.database.type == "oracle") ''
-        extension=${pkgs.phpPackages.oci8}/lib/php/extensions/oci8.so
-      '';
-      phpEnv.ZABBIX_CONFIG = "${zabbixConfig}";
-      settings = {
-        "listen.owner" = config.services.httpd.user;
-        "listen.group" = config.services.httpd.group;
-      } // cfg.poolConfig;
-    };
-
-    services.httpd = {
-      enable = true;
-      adminAddr = mkDefault cfg.virtualHost.adminAddr;
-      extraModules = [ "proxy_fcgi" ];
-      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
-        documentRoot = mkForce "${cfg.package}/share/zabbix";
-        extraConfig = ''
-          <Directory "${cfg.package}/share/zabbix">
-            <FilesMatch "\.php$">
-              <If "-f %{REQUEST_FILENAME}">
-                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
-              </If>
-            </FilesMatch>
-            AllowOverride all
-            Options -Indexes
-            DirectoryIndex index.php
-          </Directory>
-        '';
-      } ];
-    };
-
-    users.users.${user} = mapAttrs (name: mkDefault) {
-      description = "Zabbix daemon user";
-      uid = config.ids.uids.zabbix;
-      inherit group;
-    };
-
-    users.groups.${group} = mapAttrs (name: mkDefault) {
-      gid = config.ids.gids.zabbix;
-    };
-
-  };
-}
diff --git a/nixos/modules/services/web-apps/zitadel.nix b/nixos/modules/services/web-apps/zitadel.nix
deleted file mode 100644
index 99b0a0bc56f6..000000000000
--- a/nixos/modules/services/web-apps/zitadel.nix
+++ /dev/null
@@ -1,223 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-  cfg = config.services.zitadel;
-
-  settingsFormat = pkgs.formats.yaml { };
-in
-{
-  options.services.zitadel =
-    let inherit (lib) mkEnableOption mkOption mkPackageOption types;
-    in {
-      enable = mkEnableOption "ZITADEL, a user and identity access management platform";
-
-      package = mkPackageOption pkgs "ZITADEL" { default = [ "zitadel" ]; };
-
-      user = mkOption {
-        type = types.str;
-        default = "zitadel";
-        description = "The user to run ZITADEL under.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "zitadel";
-        description = "The group to run ZITADEL under.";
-      };
-
-      openFirewall = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to open the port specified in `listenPort` in the firewall.
-        '';
-      };
-
-      masterKeyFile = mkOption {
-        type = types.path;
-        description = ''
-          Path to a file containing a master encryption key for ZITADEL. The
-          key must be 32 bytes.
-        '';
-      };
-
-      tlsMode = mkOption {
-        type = types.enum [ "external" "enabled" "disabled" ];
-        default = "external";
-        example = "enabled";
-        description = ''
-          The TLS mode to use. Options are:
-
-          - enabled: ZITADEL accepts HTTPS connections directly. You must
-            configure TLS if this option is selected.
-          - external: ZITADEL forces HTTPS connections, with TLS terminated at a
-            reverse proxy.
-          - disabled: ZITADEL accepts HTTP connections only. Should only be used
-            for testing.
-        '';
-      };
-
-      settings = mkOption {
-        type = lib.types.submodule {
-          freeformType = settingsFormat.type;
-
-          options = {
-            Port = mkOption {
-              type = types.port;
-              default = 8080;
-              description = "The port that ZITADEL listens on.";
-            };
-
-            TLS = {
-              KeyPath = mkOption {
-                type = types.nullOr types.path;
-                default = null;
-                description = "Path to the TLS certificate private key.";
-              };
-              Key = mkOption {
-                type = types.nullOr types.str;
-                default = null;
-                description = ''
-                  The TLS certificate private key, as a base64-encoded string.
-
-                  Note that the contents of this option will be added to the Nix
-                  store as world-readable plain text. Set
-                  [KeyPath](#opt-services.zitadel.settings.TLS.KeyPath) instead
-                  if this is undesired.
-                '';
-              };
-              CertPath = mkOption {
-                type = types.nullOr types.path;
-                default = null;
-                description = "Path to the TLS certificate.";
-              };
-              Cert = mkOption {
-                type = types.nullOr types.str;
-                default = null;
-                description = ''
-                  The TLS certificate, as a base64-encoded string.
-
-                  Note that the contents of this option will be added to the Nix
-                  store as world-readable plain text. Set
-                  [CertPath](#opt-services.zitadel.settings.TLS.CertPath) instead
-                  if this is undesired.
-                '';
-              };
-            };
-          };
-        };
-        default = { };
-        example = lib.literalExpression ''
-          {
-            Port = 8123;
-            ExternalDomain = "example.com";
-            TLS = {
-              CertPath = "/path/to/cert.pem";
-              KeyPath = "/path/to/cert.key";
-            };
-            Database.cockroach.Host = "db.example.com";
-          };
-        '';
-        description = ''
-          Contents of the runtime configuration file. See
-          https://zitadel.com/docs/self-hosting/manage/configure for more
-          details.
-        '';
-      };
-
-      extraSettingsPaths = mkOption {
-        type = types.listOf types.path;
-        default = [ ];
-        description = ''
-          A list of paths to extra settings files. These will override the
-          values set in [settings](#opt-services.zitadel.settings). Useful if
-          you want to keep sensitive secrets out of the Nix store.
-        '';
-      };
-
-      steps = mkOption {
-        type = settingsFormat.type;
-        default = { };
-        example = lib.literalExpression ''
-          {
-            FirstInstance = {
-              InstanceName = "Example";
-              Org.Human = {
-                UserName = "foobar";
-                FirstName = "Foo";
-                LastName = "Bar";
-              };
-            };
-          }
-        '';
-        description = ''
-          Contents of the database initialization config file. See
-          https://zitadel.com/docs/self-hosting/manage/configure for more
-          details.
-        '';
-      };
-
-      extraStepsPaths = mkOption {
-        type = types.listOf types.path;
-        default = [ ];
-        description = ''
-          A list of paths to extra steps files. These will override the values
-          set in [steps](#opt-services.zitadel.steps). Useful if you want to
-          keep sensitive secrets out of the Nix store.
-        '';
-      };
-    };
-
-  config = lib.mkIf cfg.enable {
-    assertions = [{
-      assertion = cfg.tlsMode == "enabled"
-        -> ((cfg.settings.TLS.Key != null || cfg.settings.TLS.KeyPath != null)
-        && (cfg.settings.TLS.Cert != null || cfg.settings.TLS.CertPath
-        != null));
-      message = ''
-        A TLS certificate and key must be configured in
-        services.zitadel.settings.TLS if services.zitadel.tlsMode is enabled.
-      '';
-    }];
-
-    networking.firewall.allowedTCPPorts =
-      lib.mkIf cfg.openFirewall [ cfg.settings.Port ];
-
-    systemd.services.zitadel =
-      let
-        configFile = settingsFormat.generate "config.yaml" cfg.settings;
-        stepsFile = settingsFormat.generate "steps.yaml" cfg.steps;
-
-        args = lib.cli.toGNUCommandLineShell { } {
-          config = cfg.extraSettingsPaths ++ [ configFile ];
-          steps = cfg.extraStepsPaths ++ [ stepsFile ];
-          masterkeyFile = cfg.masterKeyFile;
-          inherit (cfg) tlsMode;
-        };
-      in
-      {
-        description = "ZITADEL identity access management";
-        path = [ cfg.package ];
-        wantedBy = [ "multi-user.target" ];
-
-        script = ''
-          zitadel start-from-init ${args}
-        '';
-
-        serviceConfig = {
-          Type = "simple";
-          User = cfg.user;
-          Group = cfg.group;
-          Restart = "on-failure";
-        };
-      };
-
-    users.users.zitadel = lib.mkIf (cfg.user == "zitadel") {
-      isSystemUser = true;
-      group = cfg.group;
-    };
-    users.groups.zitadel = lib.mkIf (cfg.group == "zitadel") { };
-  };
-
-  meta.maintainers = with lib.maintainers; [ Sorixelle ];
-}