summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix37
-rw-r--r--nixos/modules/module-list.nix2
-rw-r--r--nixos/modules/services/misc/portunus.nix5
-rw-r--r--nixos/modules/services/misc/redmine.nix2
-rw-r--r--nixos/modules/services/network-filesystems/kubo.nix6
-rw-r--r--nixos/modules/services/networking/jitsi-videobridge.nix19
-rw-r--r--nixos/modules/services/networking/syncthing.nix514
-rw-r--r--nixos/modules/services/security/vault-agent.nix2
-rw-r--r--nixos/modules/services/video/epgstation/default.nix6
-rw-r--r--nixos/modules/services/video/frigate.nix368
-rw-r--r--nixos/modules/services/video/go2rtc/default.nix111
-rw-r--r--nixos/modules/services/web-apps/powerdns-admin.nix3
-rw-r--r--nixos/modules/system/boot/systemd/repart.nix90
13 files changed, 855 insertions, 310 deletions
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index e22bb866927ba..00b869a7b7147 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -52,7 +52,7 @@ let
   buildMenuAdditionalParamsGrub2 = additional:
   let
     finalCfg = {
-      name = "${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}";
+      name = "${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}";
       params = "init=${config.system.build.toplevel}/init ${additional} ${toString config.boot.kernelParams}";
       image = "/boot/${config.system.boot.loader.kernelFile}";
       initrd = "/boot/initrd";
@@ -109,35 +109,35 @@ let
     DEFAULT boot
 
     LABEL boot
-    MENU LABEL ${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}
+    MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}
     LINUX /boot/${config.system.boot.loader.kernelFile}
     APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}
     INITRD /boot/${config.system.boot.loader.initrdFile}
 
     # A variant to boot with 'nomodeset'
     LABEL boot-nomodeset
-    MENU LABEL ${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (nomodeset)
+    MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (nomodeset)
     LINUX /boot/${config.system.boot.loader.kernelFile}
     APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} nomodeset
     INITRD /boot/${config.system.boot.loader.initrdFile}
 
     # A variant to boot with 'copytoram'
     LABEL boot-copytoram
-    MENU LABEL ${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (copytoram)
+    MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (copytoram)
     LINUX /boot/${config.system.boot.loader.kernelFile}
     APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} copytoram
     INITRD /boot/${config.system.boot.loader.initrdFile}
 
     # A variant to boot with verbose logging to the console
     LABEL boot-debug
-    MENU LABEL ${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (debug)
+    MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (debug)
     LINUX /boot/${config.system.boot.loader.kernelFile}
     APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} loglevel=7
     INITRD /boot/${config.system.boot.loader.initrdFile}
 
     # A variant to boot with a serial console enabled
     LABEL boot-serial
-    MENU LABEL ${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (serial console=ttyS0,115200n8)
+    MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (serial console=ttyS0,115200n8)
     LINUX /boot/${config.system.boot.loader.kernelFile}
     APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} console=ttyS0,115200n8
     INITRD /boot/${config.system.boot.loader.initrdFile}
@@ -452,6 +452,7 @@ in
 
     isoImage.isoName = mkOption {
       default = "${config.isoImage.isoBaseName}.iso";
+      type = lib.types.str;
       description = lib.mdDoc ''
         Name of the generated ISO image file.
       '';
@@ -459,6 +460,7 @@ in
 
     isoImage.isoBaseName = mkOption {
       default = config.system.nixos.distroId;
+      type = lib.types.str;
       description = lib.mdDoc ''
         Prefix of the name of the generated ISO image file.
       '';
@@ -466,6 +468,7 @@ in
 
     isoImage.compressImage = mkOption {
       default = false;
+      type = lib.types.bool;
       description = lib.mdDoc ''
         Whether the ISO image should be compressed using
         {command}`zstd`.
@@ -479,15 +482,16 @@ in
                 + lib.optionalString isAarch "-Xbcj arm"
                 + lib.optionalString (isPower && is32bit && isBigEndian) "-Xbcj powerpc"
                 + lib.optionalString (isSparc) "-Xbcj sparc";
+      type = lib.types.str;
       description = lib.mdDoc ''
         Compression settings to use for the squashfs nix store.
       '';
       example = "zstd -Xcompression-level 6";
-      type = types.str;
     };
 
     isoImage.edition = mkOption {
       default = "";
+      type = lib.types.str;
       description = lib.mdDoc ''
         Specifies which edition string to use in the volume ID of the generated
         ISO image.
@@ -497,6 +501,7 @@ in
     isoImage.volumeID = mkOption {
       # nixos-$EDITION-$RELEASE-$ARCH
       default = "nixos${optionalString (config.isoImage.edition != "") "-${config.isoImage.edition}"}-${config.system.nixos.release}-${pkgs.stdenv.hostPlatform.uname.processor}";
+      type = lib.types.str;
       description = lib.mdDoc ''
         Specifies the label or volume ID of the generated ISO image.
         Note that the label is used by stage 1 of the boot process to
@@ -527,6 +532,7 @@ in
 
     isoImage.includeSystemBuildDependencies = mkOption {
       default = false;
+      type = lib.types.bool;
       description = lib.mdDoc ''
         Set this option to include all the needed sources etc in the
         image. It significantly increases image size. Use that when
@@ -538,6 +544,7 @@ in
 
     isoImage.makeBiosBootable = mkOption {
       default = false;
+      type = lib.types.bool;
       description = lib.mdDoc ''
         Whether the ISO image should be a BIOS-bootable disk.
       '';
@@ -545,6 +552,7 @@ in
 
     isoImage.makeEfiBootable = mkOption {
       default = false;
+      type = lib.types.bool;
       description = lib.mdDoc ''
         Whether the ISO image should be an EFI-bootable volume.
       '';
@@ -552,6 +560,7 @@ in
 
     isoImage.makeUsbBootable = mkOption {
       default = false;
+      type = lib.types.bool;
       description = lib.mdDoc ''
         Whether the ISO image should be bootable from CD as well as USB.
       '';
@@ -616,8 +625,22 @@ in
       '';
     };
 
+    isoImage.prependToMenuLabel = mkOption {
+      default = "";
+      type = types.str;
+      example = "Install ";
+      description = lib.mdDoc ''
+        The string to prepend before the menu label for the NixOS system.
+        This will be directly prepended (without whitespace) to the NixOS version
+        string, like for example if it is set to `XXX`:
+
+        `XXXNixOS 99.99-pre666`
+      '';
+    };
+
     isoImage.appendToMenuLabel = mkOption {
       default = " Installer";
+      type = types.str;
       example = " Live System";
       description = lib.mdDoc ''
         The string to append after the menu label for the NixOS system.
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index d4ea5e63d924c..ff06a72ff9dd5 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1150,6 +1150,8 @@
   ./services/ttys/gpm.nix
   ./services/ttys/kmscon.nix
   ./services/video/epgstation/default.nix
+  ./services/video/go2rtc/default.nix
+  ./services/video/frigate.nix
   ./services/video/mirakurun.nix
   ./services/video/replay-sorcery.nix
   ./services/video/mediamtx.nix
diff --git a/nixos/modules/services/misc/portunus.nix b/nixos/modules/services/misc/portunus.nix
index 5504fb942968f..d188819869702 100644
--- a/nixos/modules/services/misc/portunus.nix
+++ b/nixos/modules/services/misc/portunus.nix
@@ -107,8 +107,9 @@ in
     ldap = {
       package = mkOption {
         type = types.package;
-        default = pkgs.openldap;
-        defaultText = lib.literalExpression "pkgs.openldap";
+        # needs openldap built with a libxcrypt that support crypt sha256 until https://github.com/majewsky/portunus/issues/2 is solved
+        default = pkgs.openldap.override { libxcrypt = pkgs.libxcrypt-legacy; };
+        defaultText = lib.literalExpression "pkgs.openldap.override { libxcrypt = pkgs.libxcrypt-legacy; }";
         description = lib.mdDoc "The OpenLDAP package to use.";
       };
 
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index d881ea913695c..a296fd3816bbb 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -419,7 +419,7 @@ in
         Group = cfg.group;
         TimeoutSec = "300";
         WorkingDirectory = "${cfg.package}/share/redmine";
-        ExecStart="${bundle} exec rails server webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'";
+        ExecStart="${bundle} exec rails server -u webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'";
       };
 
     };
diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix
index 2537bb1b8d80f..a5c370b5be895 100644
--- a/nixos/modules/services/network-filesystems/kubo.nix
+++ b/nixos/modules/services/network-filesystems/kubo.nix
@@ -172,8 +172,8 @@ in
 
       emptyRepo = mkOption {
         type = types.bool;
-        default = false;
-        description = lib.mdDoc "If set to true, the repo won't be initialized with help files";
+        default = true;
+        description = lib.mdDoc "If set to false, the repo will be initialized with help files";
       };
 
       settings = mkOption {
@@ -331,7 +331,7 @@ in
 
       preStart = ''
         if [[ ! -f "$IPFS_PATH/config" ]]; then
-          ipfs init ${optionalString cfg.emptyRepo "-e"}
+          ipfs init --empty-repo=${lib.boolToString cfg.emptyRepo}
         else
           # After an unclean shutdown this file may exist which will cause the config command to attempt to talk to the daemon. This will hang forever if systemd is holding our sockets open.
           rm -vf "$IPFS_PATH/api"
diff --git a/nixos/modules/services/networking/jitsi-videobridge.nix b/nixos/modules/services/networking/jitsi-videobridge.nix
index 09f2ddf92c5c7..37b0b1e5bf500 100644
--- a/nixos/modules/services/networking/jitsi-videobridge.nix
+++ b/nixos/modules/services/networking/jitsi-videobridge.nix
@@ -43,6 +43,7 @@ let
         muc_nickname = xmppConfig.mucNickname;
         disable_certificate_verification = xmppConfig.disableCertificateVerification;
       });
+      apis.rest.enabled = cfg.colibriRestApi;
     };
   };
 
@@ -50,6 +51,11 @@ let
   jvbConfig = recursiveUpdate defaultJvbConfig cfg.config;
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "services" "jitsi-videobridge" "apis" ]
+      "services.jitsi-videobridge.apis was broken and has been migrated into the boolean option services.jitsi-videobridge.colibriRestApi. It is set to false by default, setting it to true will correctly enable the private /colibri rest API."
+    )
+  ];
   options.services.jitsi-videobridge = with types; {
     enable = mkEnableOption (lib.mdDoc "Jitsi Videobridge, a WebRTC compatible video router");
 
@@ -192,14 +198,13 @@ in
       '';
     };
 
-    apis = mkOption {
-      type = with types; listOf str;
+    colibriRestApi = mkOption {
+      type = bool;
       description = lib.mdDoc ''
-        What is passed as --apis= parameter. If this is empty, "none" is passed.
-        Needed for monitoring jitsi.
+        Whether to enable the private rest API for the COLIBRI control interface.
+        Needed for monitoring jitsi, enabling scraping of the /colibri/stats endpoint.
       '';
-      default = [];
-      example = literalExpression "[ \"colibri\" \"rest\" ]";
+      default = false;
     };
   };
 
@@ -233,7 +238,7 @@ in
         "export ${toVarName name}=$(cat ${xmppConfig.passwordFile})\n"
       ) cfg.xmppConfigs))
       + ''
-        ${pkgs.jitsi-videobridge}/bin/jitsi-videobridge --apis=${if (cfg.apis == []) then "none" else concatStringsSep "," cfg.apis}
+        ${pkgs.jitsi-videobridge}/bin/jitsi-videobridge
       '';
 
       serviceConfig = {
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index 688f46c4492a9..3d41fe4013ea9 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -7,26 +7,25 @@ let
   opt = options.services.syncthing;
   defaultUser = "syncthing";
   defaultGroup = defaultUser;
-  settingsFormat = pkgs.formats.json { };
 
-  devices = mapAttrsToList (_: device: device // {
+  devices = mapAttrsToList (name: device: {
     deviceID = device.id;
-  }) cfg.settings.devices;
-
-  folders = mapAttrsToList (_: folder: folder //
-    throwIf (folder?rescanInterval || folder?watch || folder?watchDelay) ''
-      The options services.syncthing.settings.folders.<name>.{rescanInterval,watch,watchDelay}
-      were removed. Please use, respectively, {rescanIntervalS,fsWatcherEnabled,fsWatcherDelayS} instead.
-    '' {
-    devices = map (device:
-      if builtins.isString device then
-        { deviceId = cfg.settings.devices.${device}.id; }
-      else
-        device
-    ) folder.devices;
-  }) (filterAttrs (_: folder:
+    inherit (device) name addresses introducer autoAcceptFolders;
+  }) cfg.devices;
+
+  folders = mapAttrsToList ( _: folder: {
+    inherit (folder) path id label type;
+    devices = map (device: { deviceId = cfg.devices.${device}.id; }) folder.devices;
+    rescanIntervalS = folder.rescanInterval;
+    fsWatcherEnabled = folder.watch;
+    fsWatcherDelayS = folder.watchDelay;
+    ignorePerms = folder.ignorePerms;
+    ignoreDelete = folder.ignoreDelete;
+    versioning = folder.versioning;
+  }) (filterAttrs (
+    _: folder:
     folder.enable
-  ) cfg.settings.folders);
+  ) cfg.folders);
 
   updateConfig = pkgs.writers.writeDash "merge-syncthing-config" ''
     set -efu
@@ -55,10 +54,10 @@ let
     old_cfg=$(curl ${cfg.guiAddress}/rest/config)
 
     # generate the new config by merging with the NixOS config options
-    new_cfg=$(printf '%s\n' "$old_cfg" | ${pkgs.jq}/bin/jq -c ${escapeShellArg ''. * ${builtins.toJSON cfg.settings} * {
-        "devices": (${builtins.toJSON devices}${optionalString (cfg.settings.devices == {} || ! cfg.overrideDevices) " + .devices"}),
-        "folders": (${builtins.toJSON folders}${optionalString (cfg.settings.folders == {} || ! cfg.overrideFolders) " + .folders"})
-    }''})
+    new_cfg=$(printf '%s\n' "$old_cfg" | ${pkgs.jq}/bin/jq -c '. * {
+        "devices": (${builtins.toJSON devices}${optionalString (cfg.devices == {} || ! cfg.overrideDevices) " + .devices"}),
+        "folders": (${builtins.toJSON folders}${optionalString (cfg.folders == {} || ! cfg.overrideFolders) " + .folders"})
+    } * ${builtins.toJSON cfg.extraOptions}')
 
     # send the new config
     curl -X PUT -d "$new_cfg" ${cfg.guiAddress}/rest/config
@@ -100,282 +99,287 @@ in {
         default = true;
         description = mdDoc ''
           Whether to delete the devices which are not configured via the
-          [devices](#opt-services.syncthing.settings.devices) option.
+          [devices](#opt-services.syncthing.devices) option.
           If set to `false`, devices added via the web
           interface will persist and will have to be deleted manually.
         '';
       };
 
+      devices = mkOption {
+        default = {};
+        description = mdDoc ''
+          Peers/devices which Syncthing should communicate with.
+
+          Note that you can still add devices manually, but those changes
+          will be reverted on restart if [overrideDevices](#opt-services.syncthing.overrideDevices)
+          is enabled.
+        '';
+        example = {
+          bigbox = {
+            id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
+            addresses = [ "tcp://192.168.0.10:51820" ];
+          };
+        };
+        type = types.attrsOf (types.submodule ({ name, ... }: {
+          options = {
+
+            name = mkOption {
+              type = types.str;
+              default = name;
+              description = lib.mdDoc ''
+                The name of the device.
+              '';
+            };
+
+            addresses = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = lib.mdDoc ''
+                The addresses used to connect to the device.
+                If this is left empty, dynamic configuration is attempted.
+              '';
+            };
+
+            id = mkOption {
+              type = types.str;
+              description = mdDoc ''
+                The device ID. See <https://docs.syncthing.net/dev/device-ids.html>.
+              '';
+            };
+
+            introducer = mkOption {
+              type = types.bool;
+              default = false;
+              description = mdDoc ''
+                Whether the device should act as an introducer and be allowed
+                to add folders on this computer.
+                See <https://docs.syncthing.net/users/introducer.html>.
+              '';
+            };
+
+            autoAcceptFolders = mkOption {
+              type = types.bool;
+              default = false;
+              description = mdDoc ''
+                Automatically create or share folders that this device advertises at the default path.
+                See <https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format>.
+              '';
+            };
+
+          };
+        }));
+      };
+
       overrideFolders = mkOption {
         type = types.bool;
         default = true;
         description = mdDoc ''
           Whether to delete the folders which are not configured via the
-          [folders](#opt-services.syncthing.settings.folders) option.
+          [folders](#opt-services.syncthing.folders) option.
           If set to `false`, folders added via the web
           interface will persist and will have to be deleted manually.
         '';
       };
 
-      settings = mkOption {
-        type = types.submodule {
-          freeformType = settingsFormat.type;
-          options = {
-            # global options
-            options = mkOption {
-              default = {};
-              description = mdDoc ''
-                The options element contains all other global configuration options
-              '';
-              type = types.submodule ({ name, ... }: {
-                freeformType = settingsFormat.type;
-                options = {
-                  localAnnounceEnabled = mkOption {
-                    type = types.bool;
-                    default = true;
-                    description = lib.mdDoc ''
-                      Whether to send announcements to the local LAN, also use such announcements to find other devices.
-                    '';
-                  };
+      folders = mkOption {
+        default = {};
+        description = mdDoc ''
+          Folders which should be shared by Syncthing.
 
-                  localAnnouncePort = mkOption {
-                    type = types.int;
-                    default = 21027;
-                    description = lib.mdDoc ''
-                      The port on which to listen and send IPv4 broadcast announcements to.
-                    '';
-                  };
+          Note that you can still add folders manually, but those changes
+          will be reverted on restart if [overrideFolders](#opt-services.syncthing.overrideFolders)
+          is enabled.
+        '';
+        example = literalExpression ''
+          {
+            "/home/user/sync" = {
+              id = "syncme";
+              devices = [ "bigbox" ];
+            };
+          }
+        '';
+        type = types.attrsOf (types.submodule ({ name, ... }: {
+          options = {
 
-                  relaysEnabled = mkOption {
-                    type = types.bool;
-                    default = true;
-                    description = lib.mdDoc ''
-                      When true, relays will be connected to and potentially used for device to device connections.
-                    '';
-                  };
+            enable = mkOption {
+              type = types.bool;
+              default = true;
+              description = lib.mdDoc ''
+                Whether to share this folder.
+                This option is useful when you want to define all folders
+                in one place, but not every machine should share all folders.
+              '';
+            };
 
-                  urAccepted = mkOption {
-                    type = types.int;
-                    default = 0;
-                    description = lib.mdDoc ''
-                      Whether the user has accepted to submit anonymous usage data.
-                      The default, 0, mean the user has not made a choice, and Syncthing will ask at some point in the future.
-                      "-1" means no, a number above zero means that that version of usage reporting has been accepted.
-                    '';
-                  };
+            path = mkOption {
+              # TODO for release 23.05: allow relative paths again and set
+              # working directory to cfg.dataDir
+              type = types.str // {
+                check = x: types.str.check x && (substring 0 1 x == "/" || substring 0 2 x == "~/");
+                description = types.str.description + " starting with / or ~/";
+              };
+              default = name;
+              description = lib.mdDoc ''
+                The path to the folder which should be shared.
+                Only absolute paths (starting with `/`) and paths relative to
+                the [user](#opt-services.syncthing.user)'s home directory
+                (starting with `~/`) are allowed.
+              '';
+            };
 
-                  limitBandwidthInLan = mkOption {
-                    type = types.bool;
-                    default = false;
-                    description = lib.mdDoc ''
-                      Whether to apply bandwidth limits to devices in the same broadcast domain as the local device.
-                    '';
-                  };
+            id = mkOption {
+              type = types.str;
+              default = name;
+              description = lib.mdDoc ''
+                The ID of the folder. Must be the same on all devices.
+              '';
+            };
 
-                  maxFolderConcurrency = mkOption {
-                    type = types.int;
-                    default = 0;
-                    description = lib.mdDoc ''
-                      This option controls how many folders may concurrently be in I/O-intensive operations such as syncing or scanning.
-                      The mechanism is described in detail in a [separate chapter](https://docs.syncthing.net/advanced/option-max-concurrency.html).
-                    '';
-                  };
-                };
-              });
+            label = mkOption {
+              type = types.str;
+              default = name;
+              description = lib.mdDoc ''
+                The label of the folder.
+              '';
             };
 
-            # device settings
             devices = mkOption {
-              default = {};
+              type = types.listOf types.str;
+              default = [];
               description = mdDoc ''
-                Peers/devices which Syncthing should communicate with.
-
-                Note that you can still add devices manually, but those changes
-                will be reverted on restart if [overrideDevices](#opt-services.syncthing.overrideDevices)
-                is enabled.
+                The devices this folder should be shared with. Each device must
+                be defined in the [devices](#opt-services.syncthing.devices) option.
               '';
-              example = {
-                bigbox = {
-                  id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
-                  addresses = [ "tcp://192.168.0.10:51820" ];
-                };
-              };
-              type = types.attrsOf (types.submodule ({ name, ... }: {
-                freeformType = settingsFormat.type;
-                options = {
-
-                  name = mkOption {
-                    type = types.str;
-                    default = name;
-                    description = lib.mdDoc ''
-                      The name of the device.
-                    '';
-                  };
-
-                  id = mkOption {
-                    type = types.str;
-                    description = mdDoc ''
-                      The device ID. See <https://docs.syncthing.net/dev/device-ids.html>.
-                    '';
-                  };
-
-                  autoAcceptFolders = mkOption {
-                    type = types.bool;
-                    default = false;
-                    description = mdDoc ''
-                      Automatically create or share folders that this device advertises at the default path.
-                      See <https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format>.
-                    '';
-                  };
-
-                };
-              }));
             };
 
-            # folder settings
-            folders = mkOption {
-              default = {};
+            versioning = mkOption {
+              default = null;
               description = mdDoc ''
-                Folders which should be shared by Syncthing.
-
-                Note that you can still add folders manually, but those changes
-                will be reverted on restart if [overrideFolders](#opt-services.syncthing.overrideFolders)
-                is enabled.
+                How to keep changed/deleted files with Syncthing.
+                There are 4 different types of versioning with different parameters.
+                See <https://docs.syncthing.net/users/versioning.html>.
               '';
               example = literalExpression ''
-                {
-                  "/home/user/sync" = {
-                    id = "syncme";
-                    devices = [ "bigbox" ];
-                  };
-                }
+                [
+                  {
+                    versioning = {
+                      type = "simple";
+                      params.keep = "10";
+                    };
+                  }
+                  {
+                    versioning = {
+                      type = "trashcan";
+                      params.cleanoutDays = "1000";
+                    };
+                  }
+                  {
+                    versioning = {
+                      type = "staggered";
+                      fsPath = "/syncthing/backup";
+                      params = {
+                        cleanInterval = "3600";
+                        maxAge = "31536000";
+                      };
+                    };
+                  }
+                  {
+                    versioning = {
+                      type = "external";
+                      params.versionsPath = pkgs.writers.writeBash "backup" '''
+                        folderpath="$1"
+                        filepath="$2"
+                        rm -rf "$folderpath/$filepath"
+                      ''';
+                    };
+                  }
+                ]
               '';
-              type = types.attrsOf (types.submodule ({ name, ... }: {
-                freeformType = settingsFormat.type;
+              type = with types; nullOr (submodule {
                 options = {
-
-                  enable = mkOption {
-                    type = types.bool;
-                    default = true;
-                    description = lib.mdDoc ''
-                      Whether to share this folder.
-                      This option is useful when you want to define all folders
-                      in one place, but not every machine should share all folders.
-                    '';
-                  };
-
-                  path = mkOption {
-                    # TODO for release 23.05: allow relative paths again and set
-                    # working directory to cfg.dataDir
-                    type = types.str // {
-                      check = x: types.str.check x && (substring 0 1 x == "/" || substring 0 2 x == "~/");
-                      description = types.str.description + " starting with / or ~/";
-                    };
-                    default = name;
-                    description = lib.mdDoc ''
-                      The path to the folder which should be shared.
-                      Only absolute paths (starting with `/`) and paths relative to
-                      the [user](#opt-services.syncthing.user)'s home directory
-                      (starting with `~/`) are allowed.
-                    '';
-                  };
-
-                  id = mkOption {
-                    type = types.str;
-                    default = name;
-                    description = lib.mdDoc ''
-                      The ID of the folder. Must be the same on all devices.
-                    '';
-                  };
-
-                  label = mkOption {
-                    type = types.str;
-                    default = name;
-                    description = lib.mdDoc ''
-                      The label of the folder.
-                    '';
-                  };
-
-                  devices = mkOption {
-                    type = types.listOf types.str;
-                    default = [];
+                  type = mkOption {
+                    type = enum [ "external" "simple" "staggered" "trashcan" ];
                     description = mdDoc ''
-                      The devices this folder should be shared with. Each device must
-                      be defined in the [devices](#opt-services.syncthing.settings.devices) option.
+                      The type of versioning.
+                      See <https://docs.syncthing.net/users/versioning.html>.
                     '';
                   };
-
-                  versioning = mkOption {
-                    default = null;
+                  fsPath = mkOption {
+                    default = "";
+                    type = either str path;
                     description = mdDoc ''
-                      How to keep changed/deleted files with Syncthing.
-                      There are 4 different types of versioning with different parameters.
+                      Path to the versioning folder.
                       See <https://docs.syncthing.net/users/versioning.html>.
                     '';
-                    example = literalExpression ''
-                      [
-                        {
-                          versioning = {
-                            type = "simple";
-                            params.keep = "10";
-                          };
-                        }
-                        {
-                          versioning = {
-                            type = "trashcan";
-                            params.cleanoutDays = "1000";
-                          };
-                        }
-                        {
-                          versioning = {
-                            type = "staggered";
-                            fsPath = "/syncthing/backup";
-                            params = {
-                              cleanInterval = "3600";
-                              maxAge = "31536000";
-                            };
-                          };
-                        }
-                        {
-                          versioning = {
-                            type = "external";
-                            params.versionsPath = pkgs.writers.writeBash "backup" '''
-                              folderpath="$1"
-                              filepath="$2"
-                              rm -rf "$folderpath/$filepath"
-                            ''';
-                          };
-                        }
-                      ]
-                    '';
-                    type = with types; nullOr (submodule {
-                      freeformType = settingsFormat.type;
-                      options = {
-                        type = mkOption {
-                          type = enum [ "external" "simple" "staggered" "trashcan" ];
-                          description = mdDoc ''
-                            The type of versioning.
-                            See <https://docs.syncthing.net/users/versioning.html>.
-                          '';
-                        };
-                      };
-                    });
                   };
-
-                  copyOwnershipFromParent = mkOption {
-                    type = types.bool;
-                    default = false;
+                  params = mkOption {
+                    type = attrsOf (either str path);
                     description = mdDoc ''
-                      On Unix systems, tries to copy file/folder ownership from the parent directory (the directory it’s located in).
-                      Requires running Syncthing as a privileged user, or granting it additional capabilities (e.g. CAP_CHOWN on Linux).
+                      The parameters for versioning. Structure depends on
+                      [versioning.type](#opt-services.syncthing.folders._name_.versioning.type).
+                      See <https://docs.syncthing.net/users/versioning.html>.
                     '';
                   };
                 };
-              }));
+              });
+            };
+
+            rescanInterval = mkOption {
+              type = types.int;
+              default = 3600;
+              description = lib.mdDoc ''
+                How often the folder should be rescanned for changes.
+              '';
+            };
+
+            type = mkOption {
+              type = types.enum [ "sendreceive" "sendonly" "receiveonly" "receiveencrypted" ];
+              default = "sendreceive";
+              description = lib.mdDoc ''
+                Whether to only send changes for this folder, only receive them
+                or both. `receiveencrypted` can be used for untrusted devices. See
+                <https://docs.syncthing.net/users/untrusted.html> for reference.
+              '';
+            };
+
+            watch = mkOption {
+              type = types.bool;
+              default = true;
+              description = lib.mdDoc ''
+                Whether the folder should be watched for changes by inotify.
+              '';
+            };
+
+            watchDelay = mkOption {
+              type = types.int;
+              default = 10;
+              description = lib.mdDoc ''
+                The delay after an inotify event is triggered.
+              '';
+            };
+
+            ignorePerms = mkOption {
+              type = types.bool;
+              default = true;
+              description = lib.mdDoc ''
+                Whether to ignore permission changes.
+              '';
             };
 
+            ignoreDelete = mkOption {
+              type = types.bool;
+              default = false;
+              description = mdDoc ''
+                Whether to skip deleting files that are deleted by peers.
+                See <https://docs.syncthing.net/advanced/folder-ignoredelete.html>.
+              '';
+            };
           };
-        };
+        }));
+      };
+
+      extraOptions = mkOption {
+        type = types.addCheck (pkgs.formats.json {}).type isAttrs;
         default = {};
         description = mdDoc ''
           Extra configuration options for Syncthing.
@@ -526,10 +530,6 @@ in {
       This option was removed because Syncthing now has the inotify functionality included under the name "fswatcher".
       It can be enabled on a per-folder basis through the web interface.
     '')
-    (mkRenamedOptionModule [ "services" "syncthing" "extraOptions" ] [ "services" "syncthing" "settings" ])
-    (mkRenamedOptionModule [ "services" "syncthing" "folders" ] [ "services" "syncthing" "settings" "folders" ])
-    (mkRenamedOptionModule [ "services" "syncthing" "devices" ] [ "services" "syncthing" "settings" "devices" ])
-    (mkRenamedOptionModule [ "services" "syncthing" "options" ] [ "services" "syncthing" "settings" "options" ])
   ] ++ map (o:
     mkRenamedOptionModule [ "services" "syncthing" "declarative" o ] [ "services" "syncthing" o ]
   ) [ "cert" "key" "devices" "folders" "overrideDevices" "overrideFolders" "extraOptions"];
@@ -615,7 +615,9 @@ in {
           ];
         };
       };
-      syncthing-init = mkIf (cfg.settings != {}) {
+      syncthing-init = mkIf (
+        cfg.devices != {} || cfg.folders != {} || cfg.extraOptions != {}
+      ) {
         description = "Syncthing configuration updater";
         requisite = [ "syncthing.service" ];
         after = [ "syncthing.service" ];
diff --git a/nixos/modules/services/security/vault-agent.nix b/nixos/modules/services/security/vault-agent.nix
index 244004f7c9131..17b8ff83592e1 100644
--- a/nixos/modules/services/security/vault-agent.nix
+++ b/nixos/modules/services/security/vault-agent.nix
@@ -123,6 +123,6 @@ in
       })
     [ "consul-template" "vault-agent" ]);
 
-  meta.maintainers = with maintainers; [ indeednotjames tcheronneau ];
+  meta.maintainers = with maintainers; [ emilylange tcheronneau ];
 }
 
diff --git a/nixos/modules/services/video/epgstation/default.nix b/nixos/modules/services/video/epgstation/default.nix
index 3d1d7a27c216c..a395294ec1425 100644
--- a/nixos/modules/services/video/epgstation/default.nix
+++ b/nixos/modules/services/video/epgstation/default.nix
@@ -264,6 +264,9 @@ in
       description = "EPGStation user";
       group = config.users.groups.epgstation.name;
       isSystemUser = true;
+
+      # NPM insists on creating ~/.npm
+      home = "/var/cache/epgstation";
     };
 
     users.groups.epgstation = { };
@@ -318,11 +321,14 @@ in
         ++ lib.optional config.services.mirakurun.enable "mirakurun.service"
         ++ lib.optional config.services.mysql.enable "mysql.service";
 
+      environment.NODE_ENV = "production";
+
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/epgstation start";
         ExecStartPre = "+${preStartScript}";
         User = username;
         Group = groupname;
+        CacheDirectory = "epgstation";
         StateDirectory = "epgstation";
         LogsDirectory = "epgstation";
         ConfigurationDirectory = "epgstation";
diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix
new file mode 100644
index 0000000000000..217637cbebcf9
--- /dev/null
+++ b/nixos/modules/services/video/frigate.nix
@@ -0,0 +1,368 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  inherit (lib)
+    literalExpression
+    mkDefault
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkOption
+    types;
+
+  cfg = config.services.frigate;
+
+  format = pkgs.formats.yaml {};
+
+  filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! lib.elem v [ null ])) cfg.settings;
+
+  cameraFormat = with types; submodule {
+    freeformType = format.type;
+    options = {
+      ffmpeg = {
+        inputs = mkOption {
+          description = mdDoc ''
+            List of inputs for this camera.
+          '';
+          type = listOf (submodule {
+            freeformType = format.type;
+            options = {
+              path = mkOption {
+                type = str;
+                example = "rtsp://192.0.2.1:554/rtsp";
+                description = mdDoc ''
+                  Stream URL
+                '';
+              };
+              roles = mkOption {
+                type = listOf (enum [ "detect" "record" "rtmp" ]);
+                example = literalExpression ''
+                  [ "detect" "rtmp" ]
+                '';
+                description = mdDoc ''
+                  List of roles for this stream
+                '';
+              };
+            };
+          });
+        };
+      };
+    };
+  };
+
+in
+
+{
+  meta.buildDocsInSandbox = false;
+
+  options.services.frigate = with types; {
+    enable = mkEnableOption (mdDoc "Frigate NVR");
+
+    package = mkOption {
+      type = package;
+      default = pkgs.frigate;
+      description = mdDoc ''
+        The frigate package to use.
+      '';
+    };
+
+    hostname = mkOption {
+      type = str;
+      example = "frigate.exampe.com";
+      description = mdDoc ''
+        Hostname of the nginx vhost to configure.
+
+        Only nginx is supported by upstream for direct reverse proxying.
+      '';
+    };
+
+    settings = mkOption {
+      type = submodule {
+        freeformType = format.type;
+        options = {
+          cameras = mkOption {
+            type = attrsOf cameraFormat;
+            description = mdDoc ''
+              Attribute set of cameras configurations.
+
+              https://docs.frigate.video/configuration/cameras
+            '';
+          };
+
+          database = {
+            path = mkOption {
+              type = path;
+              default = "/var/lib/frigate/frigate.db";
+              description = mdDoc ''
+                Path to the SQLite database used
+              '';
+            };
+          };
+
+          mqtt = {
+            enabled = mkEnableOption (mdDoc "MQTT support");
+
+            host = mkOption {
+              type = nullOr str;
+              default = null;
+              example = "mqtt.example.com";
+              description = mdDoc ''
+                MQTT server hostname
+              '';
+            };
+          };
+        };
+      };
+      default = {};
+      description = mdDoc ''
+        Frigate configuration as a nix attribute set.
+
+        See the project documentation for how to configure frigate.
+        - [Creating a config file](https://docs.frigate.video/guides/getting_started)
+        - [Configuration reference](https://docs.frigate.video/configuration/index)
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.nginx = {
+      enable =true;
+      additionalModules = with pkgs.nginxModules; [
+        secure-token
+        rtmp
+        vod
+      ];
+      recommendedProxySettings = mkDefault true;
+      recommendedGzipSettings = mkDefault true;
+      upstreams = {
+        frigate-api.servers = {
+          "127.0.0.1:5001" = {};
+        };
+        frigate-mqtt-ws.servers = {
+          "127.0.0.1:5002" = {};
+        };
+        frigate-jsmpeg.servers = {
+          "127.0.0.1:8082" = {};
+        };
+        frigate-go2rtc.servers = {
+          "127.0.0.1:1984" = {};
+        };
+      };
+      # Based on https://github.com/blakeblackshear/frigate/blob/v0.12.0/docker/rootfs/usr/local/nginx/conf/nginx.conf
+      virtualHosts."${cfg.hostname}" = {
+        locations = {
+          "/api/" = {
+            proxyPass = "http://frigate-api/";
+          };
+          "~* /api/.*\.(jpg|jpeg|png)$" = {
+            proxyPass = "http://frigate-api";
+            extraConfig = ''
+              add_header 'Access-Control-Allow-Origin' '*';
+              add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
+              rewrite ^/api/(.*)$ $1 break;
+            '';
+          };
+          "/vod/" = {
+            extraConfig = ''
+              aio threads;
+              vod hls;
+
+              secure_token $args;
+              secure_token_types application/vnd.apple.mpegurl;
+
+              add_header Access-Control-Allow-Headers '*';
+              add_header Access-Control-Expose-Headers 'Server,range,Content-Length,Content-Range';
+              add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS';
+              add_header Access-Control-Allow-Origin '*';
+              add_header Cache-Control "no-store";
+              expires off;
+            '';
+          };
+          "/stream/" = {
+            # TODO
+          };
+          "/ws" = {
+            proxyPass = "http://frigate-mqtt-ws/";
+            proxyWebsockets = true;
+          };
+          "/live/jsmpeg" = {
+            proxyPass = "http://frigate-jsmpeg/";
+            proxyWebsockets = true;
+          };
+          "/live/mse/" = {
+            proxyPass = "http://frigate-go2rtc/";
+            proxyWebsockets = true;
+          };
+          "/live/webrtc/" = {
+            proxyPass = "http://frigate-go2rtc/";
+            proxyWebsockets = true;
+          };
+          "/cache/" = {
+            alias = "/var/cache/frigate/";
+          };
+          "/clips/" = {
+            root = "/var/lib/frigate";
+            extraConfig = ''
+              add_header 'Access-Control-Allow-Origin' "$http_origin" always;
+              add_header 'Access-Control-Allow-Credentials' 'true';
+              add_header 'Access-Control-Expose-Headers' 'Content-Length';
+              if ($request_method = 'OPTIONS') {
+                  add_header 'Access-Control-Allow-Origin' "$http_origin";
+                  add_header 'Access-Control-Max-Age' 1728000;
+                  add_header 'Content-Type' 'text/plain charset=UTF-8';
+                  add_header 'Content-Length' 0;
+                  return 204;
+              }
+
+              types {
+                  video/mp4 mp4;
+                  image/jpeg jpg;
+              }
+
+              autoindex on;
+            '';
+          };
+          "/recordings/" = {
+            root = "/var/lib/frigate";
+            extraConfig = ''
+              add_header 'Access-Control-Allow-Origin' "$http_origin" always;
+              add_header 'Access-Control-Allow-Credentials' 'true';
+              add_header 'Access-Control-Expose-Headers' 'Content-Length';
+              if ($request_method = 'OPTIONS') {
+                  add_header 'Access-Control-Allow-Origin' "$http_origin";
+                  add_header 'Access-Control-Max-Age' 1728000;
+                  add_header 'Content-Type' 'text/plain charset=UTF-8';
+                  add_header 'Content-Length' 0;
+                  return 204;
+              }
+
+              types {
+                  video/mp4 mp4;
+              }
+
+              autoindex on;
+              autoindex_format json;
+            '';
+          };
+          "/assets/" = {
+            root = cfg.package.web;
+            extraConfig = ''
+              access_log off;
+              expires 1y;
+              add_header Cache-Control "public";
+            '';
+          };
+          "/" = {
+            root = cfg.package.web;
+            tryFiles = "$uri $uri/ /index.html";
+            extraConfig = ''
+              add_header Cache-Control "no-store";
+              expires off;
+
+              sub_filter 'href="/BASE_PATH/' 'href="$http_x_ingress_path/';
+              sub_filter 'url(/BASE_PATH/' 'url($http_x_ingress_path/';
+              sub_filter '"/BASE_PATH/dist/' '"$http_x_ingress_path/dist/';
+              sub_filter '"/BASE_PATH/js/' '"$http_x_ingress_path/js/';
+              sub_filter '"/BASE_PATH/assets/' '"$http_x_ingress_path/assets/';
+              sub_filter '"/BASE_PATH/monacoeditorwork/' '"$http_x_ingress_path/assets/';
+              sub_filter 'return"/BASE_PATH/"' 'return window.baseUrl';
+              sub_filter '<body>' '<body><script>window.baseUrl="$http_x_ingress_path/";</script>';
+              sub_filter_types text/css application/javascript;
+              sub_filter_once off;
+            '';
+          };
+        };
+        extraConfig = ''
+          # vod settings
+          vod_base_url "";
+          vod_segments_base_url "";
+          vod_mode mapped;
+          vod_max_mapping_response_size 1m;
+          vod_upstream_location /api;
+          vod_align_segments_to_key_frames on;
+          vod_manifest_segment_durations_mode accurate;
+          vod_ignore_edit_list on;
+          vod_segment_duration 10000;
+          vod_hls_mpegts_align_frames off;
+          vod_hls_mpegts_interleave_frames on;
+          # file handle caching / aio
+          open_file_cache max=1000 inactive=5m;
+          open_file_cache_valid 2m;
+          open_file_cache_min_uses 1;
+          open_file_cache_errors on;
+          aio on;
+          # https://github.com/kaltura/nginx-vod-module#vod_open_file_thread_pool
+          vod_open_file_thread_pool default;
+          # vod caches
+          vod_metadata_cache metadata_cache 512m;
+          vod_mapping_cache mapping_cache 5m 10m;
+          # gzip manifest
+          gzip_types application/vnd.apple.mpegurl;
+        '';
+      };
+      appendConfig = ''
+        rtmp {
+            server {
+                listen 1935;
+                chunk_size 4096;
+                allow publish 127.0.0.1;
+                deny publish all;
+                allow play all;
+                application live {
+                    live on;
+                    record off;
+                    meta copy;
+                }
+            }
+        }
+      '';
+    };
+
+    systemd.services.frigate = {
+      after = [
+        "go2rtc.service"
+        "network.target"
+      ];
+      wantedBy = [
+        "multi-user.target"
+      ];
+      environment = {
+        CONFIG_FILE = format.generate "frigate.yml" filteredConfig;
+        HOME = "/var/lib/frigate";
+        PYTHONPATH = cfg.package.pythonPath;
+      };
+      path = with pkgs; [
+        # unfree:
+        # config.boot.kernelPackages.nvidiaPackages.latest.bin
+        ffmpeg_5-headless
+        libva-utils
+        procps
+        radeontop
+      ] ++ lib.optionals (!stdenv.isAarch64) [
+        # not available on aarch64-linux
+        intel-gpu-tools
+      ];
+      serviceConfig = {
+        ExecStart = "${cfg.package.python.interpreter} -m frigate";
+
+        DynamicUser = true;
+        User = "frigate";
+
+        StateDirectory = "frigate";
+        UMask = "0077";
+
+        # Caches
+        PrivateTmp = true;
+        CacheDirectory = "frigate";
+
+        BindPaths = [
+          "/migrations:${cfg.package}/share/frigate/migrations:ro"
+        ];
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/video/go2rtc/default.nix b/nixos/modules/services/video/go2rtc/default.nix
new file mode 100644
index 0000000000000..45506d551f1c4
--- /dev/null
+++ b/nixos/modules/services/video/go2rtc/default.nix
@@ -0,0 +1,111 @@
+{ lib
+, config
+, options
+, pkgs
+, ...
+}:
+
+let
+  inherit (lib)
+    literalExpression
+    mdDoc
+    mkEnableOption
+    mkOption
+    mkPackageOptionMD
+    types
+    ;
+
+  cfg = config.services.go2rtc;
+  opt = options.services.go2rtc;
+
+  format = pkgs.formats.yaml {};
+  configFile = format.generate "go2rtc.yaml" cfg.settings;
+in
+
+{
+  meta.buildDocsInSandbox = false;
+
+  options.services.go2rtc = with types; {
+    enable = mkEnableOption (mdDoc "go2rtc streaming server");
+
+    package = mkPackageOptionMD pkgs "go2rtc" { };
+
+    settings = mkOption {
+      default = {};
+      description = mdDoc ''
+        go2rtc configuration as a Nix attribute set.
+
+        See the [wiki](https://github.com/AlexxIT/go2rtc/wiki/Configuration) for possible configuration options.
+      '';
+      type = submodule {
+        freeformType = format.type;
+        options = {
+          # https://github.com/AlexxIT/go2rtc/blob/v1.5.0/README.md#module-api
+          api = {
+            listen = mkOption {
+              type = str;
+              default = ":1984";
+              example = "127.0.0.1:1984";
+              description = mdDoc ''
+                API listen address, conforming to a Go address string.
+              '';
+            };
+          };
+
+          # https://github.com/AlexxIT/go2rtc/blob/v1.5.0/README.md#source-ffmpeg
+          ffmpeg = {
+            bin = mkOption {
+              type = path;
+              default = "${lib.getBin pkgs.ffmpeg_6-headless}/bin/ffmpeg";
+              defaultText = literalExpression "\${lib.getBin pkgs.ffmpeg_6-headless}/bin/ffmpeg";
+              description = mdDoc ''
+                The ffmpeg package to use for transcoding.
+              '';
+            };
+          };
+
+          # TODO: https://github.com/AlexxIT/go2rtc/blob/v1.5.0/README.md#module-rtsp
+          rtsp = {
+          };
+
+          streams = mkOption {
+            type = attrsOf (either str (listOf str));
+            default = {};
+            example = literalExpression ''
+              {
+                cam1 = "onvif://admin:password@192.168.1.123:2020";
+                cam2 = "tcp://192.168.1.123:12345";
+              }
+            '';
+            description = mdDoc ''
+              Stream source configuration. Multiple source types are supported.
+
+              Check the [configuration reference](https://github.com/AlexxIT/go2rtc/blob/v${cfg.package.version}/README.md#module-streams) for possible options.
+            '';
+          };
+
+          # TODO: https://github.com/AlexxIT/go2rtc/blob/v1.5.0/README.md#module-webrtc
+          webrtc = {
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.go2rtc = {
+      after = [
+        "network-online.target"
+      ];
+      wantedBy = [
+        "multi-user.target"
+      ];
+      serviceConfig = {
+        DynamicUser = true;
+        User = "go2rtc";
+        StateDirectory = "go2rtc";
+        ExecStart = "${cfg.package}/bin/go2rtc -config ${configFile}";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/powerdns-admin.nix b/nixos/modules/services/web-apps/powerdns-admin.nix
index e9f7f41055e1d..7b6fb06e35658 100644
--- a/nixos/modules/services/web-apps/powerdns-admin.nix
+++ b/nixos/modules/services/web-apps/powerdns-admin.nix
@@ -78,7 +78,8 @@ in
       environment.PYTHONPATH = pkgs.powerdns-admin.pythonPath;
       serviceConfig = {
         ExecStart = "${pkgs.powerdns-admin}/bin/powerdns-admin --pid /run/powerdns-admin/pid ${escapeShellArgs cfg.extraArgs}";
-        ExecStartPre = "${pkgs.coreutils}/bin/env FLASK_APP=${pkgs.powerdns-admin}/share/powerdnsadmin/__init__.py ${pkgs.python3Packages.flask}/bin/flask db upgrade -d ${pkgs.powerdns-admin}/share/migrations";
+        # 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";
diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix
index 251c7e2361231..e81b3e4ff2a1b 100644
--- a/nixos/modules/system/boot/systemd/repart.nix
+++ b/nixos/modules/system/boot/systemd/repart.nix
@@ -1,4 +1,4 @@
-{ config, pkgs, lib, ... }:
+{ config, pkgs, lib, utils, ... }:
 
 let
   cfg = config.systemd.repart;
@@ -26,14 +26,29 @@ let
 in
 {
   options = {
-    boot.initrd.systemd.repart.enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // {
-      description = lib.mdDoc ''
-        Grow and add partitions to a partition table at boot time in the initrd.
-        systemd-repart only works with GPT partition tables.
-
-        To run systemd-repart after the initrd, see
-        `options.systemd.repart.enable`.
-      '';
+    boot.initrd.systemd.repart = {
+      enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // {
+        description = lib.mdDoc ''
+          Grow and add partitions to a partition table at boot time in the initrd.
+          systemd-repart only works with GPT partition tables.
+
+          To run systemd-repart after the initrd, see
+          `options.systemd.repart.enable`.
+        '';
+      };
+
+      device = lib.mkOption {
+        type = with lib.types; nullOr str;
+        description = lib.mdDoc ''
+          The device to operate on.
+
+          If `device == null`, systemd-repart will operate on the device
+          backing the root partition. So in order to dynamically *create* the
+          root partition in the initrd you need to set a device.
+        '';
+        default = null;
+        example = "/dev/vda";
+      };
     };
 
     systemd.repart = {
@@ -84,31 +99,42 @@ in
       contents."/etc/repart.d".source = definitionsDirectory;
 
       # Override defaults in upstream unit.
-      services.systemd-repart = {
-        # systemd-repart tries to create directories in /var/tmp by default to
-        # store large temporary files that benefit from persistence on disk. In
-        # the initrd, however, /var/tmp does not provide more persistence than
-        # /tmp, so we re-use it here.
-        environment."TMPDIR" = "/tmp";
-        serviceConfig = {
-          ExecStart = [
-            " " # required to unset the previous value.
-            # When running in the initrd, systemd-repart by default searches
-            # for definition files in /sysroot or /sysusr. We tell it to look
-            # in the initrd itself.
-            ''${config.boot.initrd.systemd.package}/bin/systemd-repart \
+      services.systemd-repart =
+        let
+          deviceUnit = "${utils.escapeSystemdPath initrdCfg.device}.device";
+        in
+        {
+          # systemd-repart tries to create directories in /var/tmp by default to
+          # store large temporary files that benefit from persistence on disk. In
+          # the initrd, however, /var/tmp does not provide more persistence than
+          # /tmp, so we re-use it here.
+          environment."TMPDIR" = "/tmp";
+          serviceConfig = {
+            ExecStart = [
+              " " # required to unset the previous value.
+              # When running in the initrd, systemd-repart by default searches
+              # for definition files in /sysroot or /sysusr. We tell it to look
+              # in the initrd itself.
+              ''${config.boot.initrd.systemd.package}/bin/systemd-repart \
                   --definitions=/etc/repart.d \
-                  --dry-run=no
-            ''
-          ];
+                  --dry-run=no ${lib.optionalString (initrdCfg.device != null) initrdCfg.device}
+              ''
+            ];
+          };
+          # systemd-repart needs to run after /sysroot (or /sysuser, but we
+          # don't have it) has been mounted because otherwise it cannot
+          # determine the device (i.e disk) to operate on. If you want to run
+          # systemd-repart without /sysroot (i.e. to create the root
+          # partition), you have to explicitly tell it which device to operate
+          # on. The service then needs to be ordered to run after this device
+          # is available.
+          requires = lib.mkIf (initrdCfg.device != null) [ deviceUnit ];
+          after =
+            if initrdCfg.device == null then
+              [ "sysroot.mount" ]
+            else
+              [ deviceUnit ];
         };
-        # systemd-repart needs to run after /sysroot (or /sysuser, but we don't
-        # have it) has been mounted because otherwise it cannot determine the
-        # device (i.e disk) to operate on. If you want to run systemd-repart
-        # without /sysroot, you have to explicitly tell it which device to
-        # operate on.
-        after = [ "sysroot.mount" ];
-      };
     };
 
     environment.etc = lib.mkIf cfg.enable {