about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md2
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/misc/mollysocket.nix133
-rw-r--r--nixos/modules/services/web-apps/peertube.nix292
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/mollysocket.nix27
-rw-r--r--nixos/tests/nixos-rebuild-install-bootloader.nix2
-rw-r--r--nixos/tests/web-apps/peertube.nix28
-rw-r--r--pkgs/by-name/mo/mollysocket/package.nix58
-rw-r--r--pkgs/development/python-modules/extract-msg/default.nix8
-rw-r--r--pkgs/development/python-modules/fjaraskupan/default.nix16
-rw-r--r--pkgs/development/python-modules/mypy-boto3/default.nix24
-rwxr-xr-x[-rw-r--r--]pkgs/development/python-modules/mypy-boto3/update.sh0
-rw-r--r--pkgs/development/python-modules/pyunifiprotect/default.nix12
-rw-r--r--pkgs/development/python-modules/rtfde/default.nix10
-rwxr-xr-xpkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh2
-rw-r--r--pkgs/servers/peertube/default.nix102
17 files changed, 497 insertions, 221 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
index fea452375477..01ba9038fa75 100644
--- a/nixos/doc/manual/release-notes/rl-2405.section.md
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -104,6 +104,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - [transfer-sh](https://github.com/dutchcoders/transfer.sh), a tool that supports easy and fast file sharing from the command-line. Available as [services.transfer-sh](#opt-services.transfer-sh.enable).
 
+- [MollySocket](https://github.com/mollyim/mollysocket) which allows getting Signal notifications via UnifiedPush.
+
 - [Suwayomi Server](https://github.com/Suwayomi/Suwayomi-Server), a free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org). Available as [services.suwayomi-server](#opt-services.suwayomi-server.enable).
 
 - [ping_exporter](https://github.com/czerwonk/ping_exporter), a Prometheus exporter for ICMP echo requests. Available as [services.prometheus.exporters.ping](#opt-services.prometheus.exporters.ping.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index d89d294b0469..90b37e878312 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -728,6 +728,7 @@
   ./services/misc/mbpfan.nix
   ./services/misc/mediatomb.nix
   ./services/misc/metabase.nix
+  ./services/misc/mollysocket.nix
   ./services/misc/moonraker.nix
   ./services/misc/mqtt2influxdb.nix
   ./services/misc/n8n.nix
diff --git a/nixos/modules/services/misc/mollysocket.nix b/nixos/modules/services/misc/mollysocket.nix
new file mode 100644
index 000000000000..f40caa4a782e
--- /dev/null
+++ b/nixos/modules/services/misc/mollysocket.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) getExe mkIf mkOption mkEnableOption optionals types;
+
+  cfg = config.services.mollysocket;
+  configuration = format.generate "mollysocket.conf" cfg.settings;
+  format = pkgs.formats.toml { };
+  package = pkgs.writeShellScriptBin "mollysocket" ''
+    MOLLY_CONF=${configuration} exec ${getExe pkgs.mollysocket} "$@"
+  '';
+in {
+  options.services.mollysocket = {
+    enable = mkEnableOption ''
+      [MollySocket](https://github.com/mollyim/mollysocket) for getting Signal
+      notifications via UnifiedPush
+    '';
+
+    settings = mkOption {
+      default = { };
+      description = ''
+        Configuration for MollySocket. Available options are listed
+        [here](https://github.com/mollyim/mollysocket#configuration).
+      '';
+      type = types.submodule {
+        freeformType = format.type;
+        options = {
+          host = mkOption {
+            default = "127.0.0.1";
+            description = "Listening address of the web server";
+            type = types.str;
+          };
+
+          port = mkOption {
+            default = 8020;
+            description = "Listening port of the web server";
+            type = types.port;
+          };
+
+          allowed_endpoints = mkOption {
+            default = [ "*" ];
+            description = "List of UnifiedPush servers";
+            example = [ "https://ntfy.sh" ];
+            type = with types; listOf str;
+          };
+
+          allowed_uuids = mkOption {
+            default = [ "*" ];
+            description = "UUIDs of Signal accounts that may use this server";
+            example = [ "abcdef-12345-tuxyz-67890" ];
+            type = with types; listOf str;
+          };
+        };
+      };
+    };
+
+    environmentFile = mkOption {
+      default = null;
+      description = ''
+        Environment file (see {manpage}`systemd.exec(5)` "EnvironmentFile="
+        section for the syntax) passed to the service. This option can be
+        used to safely include secrets in the configuration.
+      '';
+      example = "/run/secrets/mollysocket";
+      type = with types; nullOr path;
+    };
+
+    logLevel = mkOption {
+      default = "info";
+      description = "Set the {env}`RUST_LOG` environment variable";
+      example = "debug";
+      type = types.str;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [
+      package
+    ];
+
+    # see https://github.com/mollyim/mollysocket/blob/main/mollysocket.service
+    systemd.services.mollysocket = {
+      description = "MollySocket";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      environment.RUST_LOG = cfg.logLevel;
+      serviceConfig = let
+        capabilities = [ "" ] ++ optionals (cfg.settings.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+      in {
+        EnvironmentFile = cfg.environmentFile;
+        ExecStart = "${getExe package} server";
+        KillSignal = "SIGINT";
+        Restart = "on-failure";
+        StateDirectory = "mollysocket";
+        TimeoutStopSec = 5;
+        WorkingDirectory = "/var/lib/mollysocket";
+
+        # hardening
+        AmbientCapabilities = capabilities;
+        CapabilityBoundingSet = capabilities;
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        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";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
+        UMask = "0077";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ dotlambda ];
+}
diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
index 39c02c81c423..76f869913592 100644
--- a/nixos/modules/services/web-apps/peertube.nix
+++ b/nixos/modules/services/web-apps/peertube.nix
@@ -61,18 +61,16 @@ let
     eval -- "\$@"
   '';
 
-  peertubeCli = pkgs.writeShellScriptBin "peertube" ''
-    node ~/dist/server/tools/peertube.js $@
+  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';
   '';
 
-  nginxCommonHeaders = lib.optionalString cfg.enableWebHttps ''
-    add_header Strict-Transport-Security      'max-age=63072000; includeSubDomains';
-  '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
-    add_header Alt-Svc                        'h3=":443"; ma=86400';
-  '' + ''
-    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';
+  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 {
@@ -330,6 +328,8 @@ in {
       }
     ];
 
+    environment.systemPackages = [ cfg.package.cli ];
+
     services.peertube.settings = lib.mkMerge [
       {
         listen = {
@@ -355,12 +355,13 @@ in {
           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/";
-          videos = lib.mkDefault "/var/lib/peertube/storage/videos/";
+          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/";
@@ -428,7 +429,7 @@ in {
 
       environment = env;
 
-      path = with pkgs; [ bashInteractive ffmpeg nodejs_18 openssl yarn python3 ];
+      path = with pkgs; [ nodejs_18 yarn ffmpeg-headless openssl ];
 
       script = ''
         #!/bin/sh
@@ -456,7 +457,7 @@ in {
         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
-        npm start
+        node dist/server
       '';
       serviceConfig = {
         Type = "simple";
@@ -488,6 +489,9 @@ in {
 
     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";
 
@@ -497,14 +501,14 @@ in {
           priority = 1110;
         };
 
-        locations."= /api/v1/videos/upload-resumable" = {
+        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;
-          '';
+            client_max_body_size 0;
+            proxy_request_buffering off;
+          '' + nginxCommonHeaders;
         };
 
         locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = {
@@ -513,13 +517,11 @@ in {
           priority = 1130;
 
           extraConfig = ''
-            client_max_body_size                        12G;
-            add_header X-File-Maximum-Size              8G always;
-          '' + lib.optionalString cfg.enableWebHttps ''
-            add_header Strict-Transport-Security        'max-age=63072000; includeSubDomains';
-          '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
-            add_header Alt-Svc                          'h3=":443"; ma=86400';
-          '';
+            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)$" = {
@@ -528,13 +530,9 @@ in {
           priority = 1135;
 
           extraConfig = ''
-            client_max_body_size                        12G;
-            add_header X-File-Maximum-Size              8G always;
-          '' + lib.optionalString cfg.enableWebHttps ''
-            add_header Strict-Transport-Security        'max-age=63072000; includeSubDomains';
-          '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
-            add_header Alt-Svc                          'h3=":443"; ma=86400';
-          '';
+            client_max_body_size 12G;
+            add_header X-File-Maximum-Size 8G always;
+          '' + nginxCommonHeaders;
         };
 
         locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = {
@@ -542,32 +540,28 @@ in {
           priority = 1140;
 
           extraConfig = ''
-            client_max_body_size                        6M;
-            add_header X-File-Maximum-Size              4M always;
-          '' + lib.optionalString cfg.enableWebHttps ''
-            add_header Strict-Transport-Security        'max-age=63072000; includeSubDomains';
-          '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
-            add_header Alt-Svc                          'h3=":443"; ma=86400';
-          '';
+            client_max_body_size 6M;
+            add_header X-File-Maximum-Size 4M always;
+          '' + nginxCommonHeaders;
         };
 
         locations."@api" = {
-          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          proxyPass = "http://peertube";
           priority = 1150;
 
           extraConfig = ''
-            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
-            proxy_set_header Host                       $host;
-            proxy_set_header X-Real-IP                  $remote_addr;
+            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_connect_timeout 10m;
 
-            proxy_send_timeout                          10m;
-            proxy_read_timeout                          10m;
+            proxy_send_timeout 10m;
+            proxy_read_timeout 10m;
 
-            client_max_body_size                        100k;
-            send_timeout                                10m;
-          '';
+            client_max_body_size 100k;
+            send_timeout 10m;
+          ''+ nginxCommonHeaders;
         };
 
         # Websocket
@@ -581,7 +575,7 @@ in {
           priority = 1220;
 
           extraConfig = ''
-            proxy_read_timeout                          15m;
+            proxy_read_timeout 15m;
           '';
         };
 
@@ -591,84 +585,82 @@ in {
         };
 
         locations."@api_websocket" = {
-          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          proxyPass = "http://peertube";
           priority = 1240;
 
           extraConfig = ''
-            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
-            proxy_set_header Host                       $host;
-            proxy_set_header X-Real-IP                  $remote_addr;
-            proxy_set_header Upgrade                    $http_upgrade;
-            proxy_set_header Connection                 'upgrade';
-
-            proxy_http_version                          1.1;
-          '';
+            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';
-          '' + lib.optionalString cfg.enableWebHttps ''
-            add_header Strict-Transport-Security        'max-age=63072000; includeSubDomains';
-          '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
-            add_header Alt-Svc                          'h3=":443"; ma=86400';
-          '';
+            add_header Cache-Control 'public, max-age=604800, immutable';
+          '' + nginxCommonHeaders;
         };
 
         locations."^~ /download/" = {
-          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          proxyPass = "http://peertube";
           priority = 1410;
           extraConfig = ''
-            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
-            proxy_set_header Host                       $host;
-            proxy_set_header X-Real-IP                  $remote_addr;
+            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;
-          '';
+            proxy_limit_rate 5M;
+          '' + nginxCommonHeaders;
         };
 
-        locations."^~ /static/streaming-playlists/private/" = {
-          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+        locations."^~ /static/streaming-playlists/hls/private/" = {
+          proxyPass = "http://peertube";
           priority = 1420;
           extraConfig = ''
-            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
-            proxy_set_header Host                       $host;
-            proxy_set_header X-Real-IP                  $remote_addr;
+            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;
-          '';
+            proxy_limit_rate 5M;
+          '' + nginxCommonHeaders;
         };
 
         locations."^~ /static/web-videos/private/" = {
-          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          proxyPass = "http://peertube";
           priority = 1430;
           extraConfig = ''
-            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
-            proxy_set_header Host                       $host;
-            proxy_set_header X-Real-IP                  $remote_addr;
+            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;
-          '';
+            proxy_limit_rate 5M;
+          '' + nginxCommonHeaders;
         };
 
         locations."^~ /static/webseed/private/" = {
-          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          proxyPass = "http://peertube";
           priority = 1440;
           extraConfig = ''
-            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
-            proxy_set_header Host                       $host;
-            proxy_set_header X-Real-IP                  $remote_addr;
+            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;
-          '';
+            proxy_limit_rate 5M;
+          '' + nginxCommonHeaders;
         };
 
         locations."^~ /static/redundancy/" = {
@@ -676,33 +668,35 @@ in {
           root = cfg.settings.storage.redundancy;
           priority = 1450;
           extraConfig = ''
-            set $peertube_limit_rate                    800k;
+            set $peertube_limit_rate 800k;
 
             if ($request_uri ~ -fragmented.mp4$) {
-              set $peertube_limit_rate                  5M;
+              set $peertube_limit_rate 5M;
             }
 
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
-              add_header Access-Control-Max-Age         1728000;
-              add_header Content-Type                   'text/plain charset=UTF-8';
-              add_header Content-Length                 0;
-              return                                    204;
+              ${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;
+              access_log off;
             }
 
-            aio                                         threads;
-            sendfile                                    on;
-            sendfile_max_chunk                          1M;
+            aio threads;
+            sendfile on;
+            sendfile_max_chunk 1M;
 
-            limit_rate                                  $peertube_limit_rate;
-            limit_rate_after                            5M;
+            limit_rate $peertube_limit_rate;
+            limit_rate_after 5M;
 
-            rewrite ^/static/redundancy/(.*)$           /$1 break;
+            rewrite ^/static/redundancy/(.*)$ /$1 break;
           '';
         };
 
@@ -711,109 +705,111 @@ in {
           root = cfg.settings.storage.streaming_playlists;
           priority = 1460;
           extraConfig = ''
-            set $peertube_limit_rate                    800k;
+            set $peertube_limit_rate 800k;
 
             if ($request_uri ~ -fragmented.mp4$) {
-              set $peertube_limit_rate                  5M;
+              set $peertube_limit_rate 5M;
             }
 
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
-              add_header Access-Control-Max-Age         1728000;
-              add_header Content-Type                   'text/plain charset=UTF-8';
-              add_header Content-Length                 0;
-              return                                    204;
+              ${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;
+              access_log off;
             }
 
-            aio                                         threads;
-            sendfile                                    on;
-            sendfile_max_chunk                          1M;
+            aio threads;
+            sendfile on;
+            sendfile_max_chunk 1M;
 
-            limit_rate                                  $peertube_limit_rate;
-            limit_rate_after                            5M;
+            limit_rate $peertube_limit_rate;
+            limit_rate_after 5M;
 
-            rewrite ^/static/streaming-playlists/(.*)$  /$1 break;
+            rewrite ^/static/streaming-playlists/(.*)$ /$1 break;
           '';
         };
 
         locations."^~ /static/web-videos/" = {
           tryFiles = "$uri @api";
-          root = cfg.settings.storage.streaming_playlists;
+          root = cfg.settings.storage.web_videos;
           priority = 1470;
           extraConfig = ''
-            set $peertube_limit_rate                    800k;
+            set $peertube_limit_rate 800k;
 
             if ($request_uri ~ -fragmented.mp4$) {
-              set $peertube_limit_rate                  5M;
+              set $peertube_limit_rate 5M;
             }
 
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
-              add_header Access-Control-Max-Age         1728000;
-              add_header Content-Type                   'text/plain charset=UTF-8';
-              add_header Content-Length                 0;
-              return                                    204;
+              ${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;
+              access_log off;
             }
 
-            aio                                         threads;
-            sendfile                                    on;
-            sendfile_max_chunk                          1M;
+            aio threads;
+            sendfile on;
+            sendfile_max_chunk 1M;
 
-            limit_rate                                  $peertube_limit_rate;
-            limit_rate_after                            5M;
+            limit_rate $peertube_limit_rate;
+            limit_rate_after 5M;
 
-            rewrite ^/static/streaming-playlists/(.*)$  /$1 break;
+            rewrite ^/static/web-videos/(.*)$ /$1 break;
           '';
         };
 
         locations."^~ /static/webseed/" = {
           tryFiles = "$uri @api";
-          root = cfg.settings.storage.videos;
+          root = cfg.settings.storage.web_videos;
           priority = 1480;
           extraConfig = ''
-            set $peertube_limit_rate                    800k;
+            set $peertube_limit_rate 800k;
 
             if ($request_uri ~ -fragmented.mp4$) {
-              set $peertube_limit_rate                  5M;
+              set $peertube_limit_rate 5M;
             }
 
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
-              add_header Access-Control-Max-Age         1728000;
-              add_header Content-Type                   'text/plain charset=UTF-8';
-              add_header Content-Length                 0;
-              return                                    204;
+              ${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;
+              access_log off;
             }
 
-            aio                                         threads;
-            sendfile                                    on;
-            sendfile_max_chunk                          1M;
+            aio threads;
+            sendfile on;
+            sendfile_max_chunk 1M;
 
-            limit_rate                                  $peertube_limit_rate;
-            limit_rate_after                            5M;
+            limit_rate $peertube_limit_rate;
+            limit_rate_after 5M;
 
-            rewrite ^/static/webseed/(.*)$              /$1 break;
+            rewrite ^/static/webseed/(.*)$ /web-videos/$1 break;
           '';
         };
-
-        extraConfig = lib.optionalString cfg.enableWebHttps ''
-          add_header Strict-Transport-Security          'max-age=63072000; includeSubDomains';
-        '';
       };
     };
 
@@ -848,7 +844,7 @@ in {
           home = cfg.package;
         };
       })
-      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs_18 pkgs.yarn ])
+      (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" ];})
     ];
 
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 9cff268ae1d1..f7ad6c16f587 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -543,6 +543,7 @@ in {
   mobilizon = handleTest ./mobilizon.nix {};
   mod_perl = handleTest ./mod_perl.nix {};
   molly-brown = handleTest ./molly-brown.nix {};
+  mollysocket = handleTest ./mollysocket.nix { };
   monado = handleTest ./monado.nix {};
   monetdb = handleTest ./monetdb.nix {};
   monica = handleTest ./web-apps/monica.nix {};
diff --git a/nixos/tests/mollysocket.nix b/nixos/tests/mollysocket.nix
new file mode 100644
index 000000000000..8cbd0c0272e0
--- /dev/null
+++ b/nixos/tests/mollysocket.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  port = 1234;
+in {
+  name = "mollysocket";
+  meta.maintainers = with lib.maintainers; [ dotlambda ];
+
+  nodes.mollysocket = { ... }: {
+    services.mollysocket = {
+      enable = true;
+      settings = {
+        inherit port;
+      };
+    };
+  };
+
+  testScript = ''
+    import json
+
+    mollysocket.wait_for_unit("mollysocket.service")
+    mollysocket.wait_for_open_port(${toString port})
+
+    out = mollysocket.succeed("curl --fail http://127.0.0.1:${toString port}")
+    assert json.loads(out)["mollysocket"]["version"] == "${toString pkgs.mollysocket.version}"
+  '';
+})
diff --git a/nixos/tests/nixos-rebuild-install-bootloader.nix b/nixos/tests/nixos-rebuild-install-bootloader.nix
index 3ade90ea24a7..94554a93bd63 100644
--- a/nixos/tests/nixos-rebuild-install-bootloader.nix
+++ b/nixos/tests/nixos-rebuild-install-bootloader.nix
@@ -60,7 +60,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       # Need to run `nixos-rebuild` twice because the first run will install
       # GRUB anyway
       with subtest("Switch system again and install bootloader"):
-          result = machine.succeed("nixos-rebuild switch --install-bootloader")
+          result = machine.succeed("nixos-rebuild switch --install-bootloader 2>&1")
           # install-grub2.pl messages
           assert "updating GRUB 2 menu..." in result
           assert "installing the GRUB 2 boot loader on /dev/vda..." in result
diff --git a/nixos/tests/web-apps/peertube.nix b/nixos/tests/web-apps/peertube.nix
index 0e5f39c08a02..83c7cf03701e 100644
--- a/nixos/tests/web-apps/peertube.nix
+++ b/nixos/tests/web-apps/peertube.nix
@@ -17,16 +17,18 @@ import ../make-test-python.nix ({pkgs, ...}:
       services.postgresql = {
         enable = true;
         enableTCPIP = true;
+        ensureDatabases = [ "peertube_test" ];
+        ensureUsers = [
+          {
+            name = "peertube_test";
+            ensureDBOwnership = true;
+          }
+        ];
         authentication = ''
-          hostnossl peertube_local peertube_test 192.168.2.11/32 md5
+          hostnossl peertube_test peertube_test 192.168.2.11/32 md5
         '';
         initialScript = pkgs.writeText "postgresql_init.sql" ''
           CREATE ROLE peertube_test LOGIN PASSWORD '0gUN0C1mgST6czvjZ8T9';
-          CREATE DATABASE peertube_local TEMPLATE template0 ENCODING UTF8;
-          GRANT ALL PRIVILEGES ON DATABASE peertube_local TO peertube_test;
-          \connect peertube_local
-          CREATE EXTENSION IF NOT EXISTS pg_trgm;
-          CREATE EXTENSION IF NOT EXISTS unaccent;
         '';
       };
 
@@ -41,6 +43,9 @@ import ../make-test-python.nix ({pkgs, ...}:
     server = { pkgs, ... }: {
       environment = {
         etc = {
+          "peertube/password-init-root".text = ''
+            PT_INITIAL_ROOT_PASSWORD=zw4SqYVdcsXUfRX8aaFX
+          '';
           "peertube/secrets-peertube".text = ''
             063d9c60d519597acef26003d5ecc32729083965d09181ef3949200cbe5f09ee
           '';
@@ -70,13 +75,15 @@ import ../make-test-python.nix ({pkgs, ...}:
         localDomain = "peertube.local";
         enableWebHttps = false;
 
+        serviceEnvironmentFile = "/etc/peertube/password-init-root";
+
         secrets = {
           secretsFile = "/etc/peertube/secrets-peertube";
         };
 
         database = {
           host = "192.168.2.10";
-          name = "peertube_local";
+          name = "peertube_test";
           user = "peertube_test";
           passwordFile = "/etc/peertube/password-posgressql-db";
         };
@@ -99,7 +106,7 @@ import ../make-test-python.nix ({pkgs, ...}:
     };
 
     client = {
-      environment.systemPackages = [ pkgs.jq ];
+      environment.systemPackages = [ pkgs.jq pkgs.peertube.cli ];
       networking = {
        interfaces.eth1 = {
           ipv4.addresses = [
@@ -130,7 +137,10 @@ import ../make-test-python.nix ({pkgs, ...}:
     client.succeed("curl --fail http://peertube.local:9000/api/v1/config/about | jq -r '.instance.name' | grep 'PeerTube\ Test\ Server'")
 
     # Check PeerTube CLI version
-    assert "${pkgs.peertube.version}" in server.succeed('su - peertube -s /bin/sh -c "peertube --version"')
+    client.succeed('peertube-cli auth add -u "http://peertube.local:9000" -U "root" --password "zw4SqYVdcsXUfRX8aaFX"')
+    client.succeed('peertube-cli auth list | grep "http://peertube.local:9000"')
+    client.succeed('peertube-cli auth del "http://peertube.local:9000"')
+    client.fail('peertube-cli auth list | grep "http://peertube.local:9000"')
 
     client.shutdown()
     server.shutdown()
diff --git a/pkgs/by-name/mo/mollysocket/package.nix b/pkgs/by-name/mo/mollysocket/package.nix
new file mode 100644
index 000000000000..025bb34e8dd7
--- /dev/null
+++ b/pkgs/by-name/mo/mollysocket/package.nix
@@ -0,0 +1,58 @@
+{ lib
+, rustPlatform
+, fetchFromGitHub
+, pkg-config
+, openssl
+, sqlite
+, stdenv
+, darwin
+, nixosTests
+}:
+
+rustPlatform.buildRustPackage rec {
+  pname = "mollysocket";
+  version = "1.3.0";
+
+  src = fetchFromGitHub {
+    owner = "mollyim";
+    repo = "mollysocket";
+    rev = version;
+    hash = "sha256-eFvRjGUQ1AU+kXUp6YALm1lqhTMY2DxvFuf+MHCL38c=";
+  };
+
+  cargoHash = "sha256-3UwvnbHH6v1fJyivdU55GmJ2/+RSqXfBKIcOARASWbE=";
+
+  nativeBuildInputs = [
+    pkg-config
+  ];
+
+  buildInputs = [
+    openssl
+    sqlite
+  ] ++ lib.optionals stdenv.isDarwin [
+    darwin.apple_sdk.frameworks.Security
+  ];
+
+  checkFlags = [
+    # tests interact with Signal servers
+    "--skip=config::tests::check_wildcard_endpoint"
+    "--skip=utils::post_allowed::tests::test_allowed"
+    "--skip=utils::post_allowed::tests::test_not_allowed"
+    "--skip=utils::post_allowed::tests::test_post"
+    "--skip=ws::tls::tests::connect_untrusted_server"
+    "--skip=ws::tls::tests::connect_trusted_server"
+  ];
+
+  passthru.tests = {
+    inherit (nixosTests) mollysocket;
+  };
+
+  meta = {
+    changelog = "https://github.com/mollyim/mollysocket/releases/tag/${version}";
+    description = "Get Signal notifications via UnifiedPush";
+    homepage = "https://github.com/mollyim/mollysocket";
+    license = lib.licenses.agpl3Plus;
+    mainProgram = "mollysocket";
+    maintainers = with lib.maintainers; [ dotlambda ];
+  };
+}
diff --git a/pkgs/development/python-modules/extract-msg/default.nix b/pkgs/development/python-modules/extract-msg/default.nix
index 592172fc3d25..6c6e23370261 100644
--- a/pkgs/development/python-modules/extract-msg/default.nix
+++ b/pkgs/development/python-modules/extract-msg/default.nix
@@ -16,7 +16,7 @@
 
 buildPythonPackage rec {
   pname = "extract-msg";
-  version = "0.48.3";
+  version = "0.48.4";
   pyproject = true;
 
   disabled = pythonOlder "3.7";
@@ -25,7 +25,7 @@ buildPythonPackage rec {
     owner = "TeamMsgExtractor";
     repo = "msg-extractor";
     rev = "refs/tags/v${version}";
-    hash = "sha256-oN5blYU8LR2O1LEb6naL33UXjBk9xpINl4h6HSaN7PQ=";
+    hash = "sha256-xX25RVtkUFn+j9rALOHQOTRzqJXiEMn7i9pxCJ8so4U=";
   };
 
   pythonRelaxDeps = [
@@ -33,12 +33,12 @@ buildPythonPackage rec {
     "red-black-tree-mod"
   ];
 
-  nativeBuildInputs = [
+  build-system = [
     pythonRelaxDepsHook
     setuptools
   ];
 
-  propagatedBuildInputs = [
+  dependencies = [
     beautifulsoup4
     compressed-rtf
     ebcdic
diff --git a/pkgs/development/python-modules/fjaraskupan/default.nix b/pkgs/development/python-modules/fjaraskupan/default.nix
index 3068d6542ed7..533847786c89 100644
--- a/pkgs/development/python-modules/fjaraskupan/default.nix
+++ b/pkgs/development/python-modules/fjaraskupan/default.nix
@@ -5,23 +5,28 @@
 , pytest-mock
 , pytestCheckHook
 , pythonOlder
+, setuptools
 }:
 
 buildPythonPackage rec {
   pname = "fjaraskupan";
-  version = "2.2.0";
-  format = "setuptools";
+  version = "2.3.0";
+  pyproject = true;
 
   disabled = pythonOlder "3.8";
 
   src = fetchFromGitHub {
     owner = "elupus";
-    repo = pname;
+    repo = "fjaraskupan";
     rev = "refs/tags/${version}";
-    hash = "sha256-0tNLwYckWF9GjhniEkiO+A+xDsUriUsMFZxG/FmUfps=";
+    hash = "sha256-3jw42lsCwNkFptMNpnhtbrPIkZP/8lUCcMigzq8Hbc4=";
   };
 
-  propagatedBuildInputs = [
+  nativeBuildInputs = [
+    setuptools
+  ];
+
+  dependencies = [
     bleak
   ];
 
@@ -37,6 +42,7 @@ buildPythonPackage rec {
   meta = with lib; {
     description = "Module for controlling Fjäråskupan kitchen fans";
     homepage = "https://github.com/elupus/fjaraskupan";
+    changelog = "https://github.com/elupus/fjaraskupan/releases/tag/${version}";
     license = with licenses; [ mit ];
     maintainers = with maintainers; [ fab ];
   };
diff --git a/pkgs/development/python-modules/mypy-boto3/default.nix b/pkgs/development/python-modules/mypy-boto3/default.nix
index 74b48b53458e..8a8d53bc1c49 100644
--- a/pkgs/development/python-modules/mypy-boto3/default.nix
+++ b/pkgs/development/python-modules/mypy-boto3/default.nix
@@ -111,7 +111,7 @@ rec {
 
   mypy-boto3-backupstorage = buildMypyBoto3Package "backupstorage" "1.34.0" "sha256-Y8kjZ+ov8OsiJ8Sm1LlvP8YbgVc+AkLkbZIhOh4y7ZY=";
 
-  mypy-boto3-batch = buildMypyBoto3Package "batch" "1.34.59" "sha256-rsXdh8f3KRAROftePejdLxChRqtiaDFsJyhctX7jRUQ=";
+  mypy-boto3-batch = buildMypyBoto3Package "batch" "1.34.72" "sha256-ha5OZVVcO/+slxQOPIrd+D1Ehaw6YpGqCWofSgFj5JI=";
 
   mypy-boto3-billingconductor = buildMypyBoto3Package "billingconductor" "1.34.1" "sha256-uXxQkoe2u3idcYta9YFbjxoK8HsvUiRQSyYrYhVi1kU=";
 
@@ -119,7 +119,7 @@ rec {
 
   mypy-boto3-budgets = buildMypyBoto3Package "budgets" "1.34.0" "sha256-gs8JYnpFNOMWppBO2R3DO+c6RecQC0vzaox5DqXCKOA=";
 
-  mypy-boto3-ce = buildMypyBoto3Package "ce" "1.34.52" "sha256-KHimN6KC8V6nyp6ZQbv0XAuWMjGZkg/bPrQ6do5QQE0=";
+  mypy-boto3-ce = buildMypyBoto3Package "ce" "1.34.71" "sha256-VfB823/q+ie97Bv5GXhsBaGxAuXSDbfFq2rO6TjThXY=";
 
   mypy-boto3-chime = buildMypyBoto3Package "chime" "1.34.0" "sha256-/IBkHJf4t1K/Ubdf/hUw5XToNBTCziMfTSdksxMwA2Q=";
 
@@ -161,7 +161,7 @@ rec {
 
   mypy-boto3-codeartifact = buildMypyBoto3Package "codeartifact" "1.34.68" "sha256-Ey0cmx0OxN1/VXIyvn0EOBP9qYIuc/XyFVZniHLaNEY=";
 
-  mypy-boto3-codebuild = buildMypyBoto3Package "codebuild" "1.34.67" "sha256-Kvd8zAHfepA4dulpiQCaT2pfKCH567d6CYd5QlweXIY=";
+  mypy-boto3-codebuild = buildMypyBoto3Package "codebuild" "1.34.70" "sha256-lv69lhMKJHRnooVrmGinfDEi7eVEe7O12GNNo5uZQQc=";
 
   mypy-boto3-codecatalyst = buildMypyBoto3Package "codecatalyst" "1.34.0" "sha256-TsXVy8bx6kaj84PJiNNU+075Tx3WW0mrtZFOyLx9yT4=";
 
@@ -251,7 +251,7 @@ rec {
 
   mypy-boto3-ebs = buildMypyBoto3Package "ebs" "1.34.0" "sha256-xIrrXOayZed+Jcn4CFXXNgKz/G+RdiuwA04wq+Ry/fs=";
 
-  mypy-boto3-ec2 = buildMypyBoto3Package "ec2" "1.34.66" "sha256-Io0ExXqdar+5A4H66ryaApWIQnEcspQysfBsOit4WyY=";
+  mypy-boto3-ec2 = buildMypyBoto3Package "ec2" "1.34.71" "sha256-hjEJNB8/m1yE9f0yxoKZeVySRfCun1NGmL8UeqP8AXs=";
 
   mypy-boto3-ec2-instance-connect = buildMypyBoto3Package "ec2-instance-connect" "1.34.63" "sha256-kExmGXEJ5jrvOewmWx7AjVb3boD5GU0cEUp/2PQhzlw=";
 
@@ -259,7 +259,7 @@ rec {
 
   mypy-boto3-ecr-public = buildMypyBoto3Package "ecr-public" "1.34.0" "sha256-38ZiRVPr9L+KUF6oL23xsIiKMW0pT/nIngFkhSS3z2Y=";
 
-  mypy-boto3-ecs = buildMypyBoto3Package "ecs" "1.34.39" "sha256-s3YTAoZSgDHenHa54DwSNRRAnfCNMcdmZP1FX0hTygE=";
+  mypy-boto3-ecs = buildMypyBoto3Package "ecs" "1.34.71" "sha256-Ka2nMhArorYcIx+MoLN7bIbKl4ptNER6uC9FdLWZBfI=";
 
   mypy-boto3-efs = buildMypyBoto3Package "efs" "1.34.0" "sha256-VAK7mfnPBPDC8Azm6Bxl86E8CkeArTmfgqYkIcSblYA=";
 
@@ -267,7 +267,7 @@ rec {
 
   mypy-boto3-elastic-inference = buildMypyBoto3Package "elastic-inference" "1.34.0" "sha256-gbWKw0zDQf3qBlp1KeO7MX1j/GqRUpFAxLG0BKFrHBk=";
 
-  mypy-boto3-elasticache = buildMypyBoto3Package "elasticache" "1.34.60" "sha256-D3WLH1FS8dddD6WKUmQApvtehGMmjWYzdaMwTHzvzYk=";
+  mypy-boto3-elasticache = buildMypyBoto3Package "elasticache" "1.34.72" "sha256-yZd2KB7wIw23PybblyIlCo/5IEFYxAUfbLD2J91eOzw=";
 
   mypy-boto3-elasticbeanstalk = buildMypyBoto3Package "elasticbeanstalk" "1.34.0" "sha256-ftVFUwY81mg/9zJ4xxVjhXF1HgKpzj1koIS32cMKRLw=";
 
@@ -279,7 +279,7 @@ rec {
 
   mypy-boto3-emr = buildMypyBoto3Package "emr" "1.34.44" "sha256-zM1VpAaBSxqdZiSrNiaAKfvliNRXMLEmvFvXcFmkZO0=";
 
-  mypy-boto3-emr-containers = buildMypyBoto3Package "emr-containers" "1.34.0" "sha256-tGHWoMVrfH35hLgzDGMSJs5kRTWQqjM/e0IAPe4EfiU=";
+  mypy-boto3-emr-containers = buildMypyBoto3Package "emr-containers" "1.34.70" "sha256-uZADsQWfrkoVrQZosfqogcKERWsykIqdk+tJpgmcai4=";
 
   mypy-boto3-emr-serverless = buildMypyBoto3Package "emr-serverless" "1.34.0" "sha256-YgccYi2+XhKiPGCMimrCooYPRV+iRuA1h120UdqJKUc=";
 
@@ -291,7 +291,7 @@ rec {
 
   mypy-boto3-evidently = buildMypyBoto3Package "evidently" "1.34.0" "sha256-MkBB5iTYJYg2cWFYHR3Qu7TcsDglLPEw0MnoHqij6+A=";
 
-  mypy-boto3-finspace = buildMypyBoto3Package "finspace" "1.34.66" "sha256-G5FMKm9HymvRPtkjvYZt6NVhPUVuBwCR4kQq8/naUFs=";
+  mypy-boto3-finspace = buildMypyBoto3Package "finspace" "1.34.71" "sha256-bgPwDXqu73DjQCADmjTig6kLNOWvQ39flwhyYAbTai4=";
 
   mypy-boto3-finspace-data = buildMypyBoto3Package "finspace-data" "1.34.0" "sha256-8mND5BbdKY5srFwdpxSyfCUTIP4fa9hztP4daUJOB8k=";
 
@@ -315,7 +315,7 @@ rec {
 
   mypy-boto3-glacier = buildMypyBoto3Package "glacier" "1.34.0" "sha256-j8LUD8EjjRL1av7UEXBqNPEARaSFgstaioGJtbel4oM=";
 
-  mypy-boto3-globalaccelerator = buildMypyBoto3Package "globalaccelerator" "1.34.0" "sha256-hvQeuu1TuLO9aB1+lWTGPgpGqoZlC8d3Ru5S4OVLgys=";
+  mypy-boto3-globalaccelerator = buildMypyBoto3Package "globalaccelerator" "1.34.70" "sha256-7Su+rgV6KD9I4j630Qybufwn39rp/8tYQ2ldEe2Untc=";
 
   mypy-boto3-glue = buildMypyBoto3Package "glue" "1.34.35" "sha256-+Kvk8uB9KZp7mw3sMAM6mHdBTnkO5J8nSVClttndMDY=";
 
@@ -469,7 +469,7 @@ rec {
 
   mypy-boto3-mediaconvert = buildMypyBoto3Package "mediaconvert" "1.34.33" "sha256-7OwfjcWDE1AHvpyOu3fE5YBwjQscpa+VnE7eylk1unA=";
 
-  mypy-boto3-medialive = buildMypyBoto3Package "medialive" "1.34.47" "sha256-kE93r3oDtz+uwUDeWBJA2yQBx4HFAL49FdpIexVMbrc=";
+  mypy-boto3-medialive = buildMypyBoto3Package "medialive" "1.34.70" "sha256-WMBmgEckAoWpwe/YMJsVRZnbkT8dbr8F1S3AW1PRF+4=";
 
   mypy-boto3-mediapackage = buildMypyBoto3Package "mediapackage" "1.34.0" "sha256-4DJ2zVk0satmVn+TZdDExx/+ClJpc1bdmbvl72Joe5U=";
 
@@ -625,7 +625,7 @@ rec {
 
   mypy-boto3-s3outposts = buildMypyBoto3Package "s3outposts" "1.34.0" "sha256-xLuGP9Fe0S7zRimt1AKd9KOrytmNd/GTRg5OVi5Xpos=";
 
-  mypy-boto3-sagemaker = buildMypyBoto3Package "sagemaker" "1.34.64" "sha256-Sf1T1aqRMIEZmuA2KH5tzuhTDN8yfFID/+h0DCWxws0=";
+  mypy-boto3-sagemaker = buildMypyBoto3Package "sagemaker" "1.34.70" "sha256-WON2j0ZQ9x3qq1mOOzMvT8jJSuJipDHDp4IxsB88GCg=";
 
   mypy-boto3-sagemaker-a2i-runtime = buildMypyBoto3Package "sagemaker-a2i-runtime" "1.34.0" "sha256-jMZ3aWKQPhNec4A/02S1waQi6Mx9JVdENc3kblhsKjA=";
 
@@ -647,7 +647,7 @@ rec {
 
   mypy-boto3-sdb = buildMypyBoto3Package "sdb" "1.34.0" "sha256-13BuAQD8uDwwDhCw+8O3V882H6/oor5Z8mBmjb7HHAU=";
 
-  mypy-boto3-secretsmanager = buildMypyBoto3Package "secretsmanager" "1.34.63" "sha256-oZM3PXGPdH1Th4tcx/7y6bj944kuR4isaQ/SsWo1Vkw=";
+  mypy-boto3-secretsmanager = buildMypyBoto3Package "secretsmanager" "1.34.72" "sha256-0HM8W1Potee9oA9LQu2ErxLjaiDISJF+ScFzoEIu8Dw=";
 
   mypy-boto3-securityhub = buildMypyBoto3Package "securityhub" "1.34.69" "sha256-2fJx1VaOhYSjTXAEboBhHhMdTH697zcGHmrJsGknDTI=";
 
diff --git a/pkgs/development/python-modules/mypy-boto3/update.sh b/pkgs/development/python-modules/mypy-boto3/update.sh
index f45789baecb2..f45789baecb2 100644..100755
--- a/pkgs/development/python-modules/mypy-boto3/update.sh
+++ b/pkgs/development/python-modules/mypy-boto3/update.sh
diff --git a/pkgs/development/python-modules/pyunifiprotect/default.nix b/pkgs/development/python-modules/pyunifiprotect/default.nix
index 9cf0773b0cb5..5356fd2e4ac0 100644
--- a/pkgs/development/python-modules/pyunifiprotect/default.nix
+++ b/pkgs/development/python-modules/pyunifiprotect/default.nix
@@ -13,6 +13,7 @@
 , orjson
 , packaging
 , pillow
+, platformdirs
 , poetry-core
 , py
 , pydantic
@@ -32,7 +33,7 @@
 
 buildPythonPackage rec {
   pname = "pyunifiprotect";
-  version = "5.0.2";
+  version = "5.1.1";
   pyproject = true;
 
   disabled = pythonOlder "3.9";
@@ -41,22 +42,22 @@ buildPythonPackage rec {
     owner = "briis";
     repo = "pyunifiprotect";
     rev = "refs/tags/v${version}";
-    hash = "sha256-bZjfpatw4lcOgMCqung/DMfRijxwtuIht6QusIYaCQ0=";
+    hash = "sha256-7D6/mIU3M4dfIa+lCgcc3APKVPCsZei+4Er+sxIxcHo=";
   };
 
   env.SETUPTOOLS_SCM_PRETEND_VERSION = version;
 
   postPatch = ''
     substituteInPlace pyproject.toml \
-      --replace "--strict-markers -ra -Wd --ignore=.* --no-cov-on-fail --cov=pyunifiprotect --cov-append --maxfail=10 -n=auto" ""
+      --replace-fail "--strict-markers -ra -Wd --ignore=.* --no-cov-on-fail --cov=pyunifiprotect --cov-append --maxfail=10 -n=auto" ""
   '';
 
-  nativeBuildInputs = [
+  build-system = [
     hatch-vcs
     hatchling
   ];
 
-  propagatedBuildInputs = [
+  dependencies = [
     aiofiles
     aiohttp
     aioshutil
@@ -64,6 +65,7 @@ buildPythonPackage rec {
     orjson
     packaging
     pillow
+    platformdirs
     pydantic
     pyjwt
     pytz
diff --git a/pkgs/development/python-modules/rtfde/default.nix b/pkgs/development/python-modules/rtfde/default.nix
index c986d7b0c5e3..3b11adc34a1a 100644
--- a/pkgs/development/python-modules/rtfde/default.nix
+++ b/pkgs/development/python-modules/rtfde/default.nix
@@ -23,11 +23,17 @@ buildPythonPackage rec {
     hash = "sha256-ai9JQ3gphY/IievBNdHiblIpc0IPS9wp7CVvBIRzG/4=";
   };
 
-  nativeBuildInputs = [
+  postPatch = ''
+    # https://github.com/seamustuohy/RTFDE/issues/31
+    substituteInPlace setup.py \
+      --replace-fail "==" ">="
+  '';
+
+  build-system = [
     setuptools
   ];
 
-  propagatedBuildInputs = [
+  dependencies = [
     lark
     oletools
   ];
diff --git a/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh b/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh
index 1868f6b2af1b..248dc7213888 100755
--- a/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh
+++ b/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh
@@ -756,7 +756,7 @@ if [[ "$action" = switch || "$action" = boot || "$action" = test || "$action" =
         "-E" "NIXOS_INSTALL_BOOTLOADER=$installBootloader"
         "--collect"
         "--no-ask-password"
-        "--pty"
+        "--pipe"
         "--quiet"
         "--same-dir"
         "--service-type=exec"
diff --git a/pkgs/servers/peertube/default.nix b/pkgs/servers/peertube/default.nix
index bc8dbde49845..871f9abb74b8 100644
--- a/pkgs/servers/peertube/default.nix
+++ b/pkgs/servers/peertube/default.nix
@@ -17,27 +17,27 @@ let
     x86_64-linux = {
       arch = "linux-x64";
       libc = "glibc";
-      hash = "sha256-I1ceMi7h6flvKBmMIU1qjAU1S6z5MzguHDul3g1zMKw=";
+      hash = "sha256-C5N6VgFtXPLLjZt0ZdRTX095njRIT+12ONuUaBBj7fQ=";
     };
     aarch64-linux = {
       arch = "linux-arm64";
       libc = "glibc";
-      hash = "sha256-q8BR7kILYV8i8ozDkpcuKarf4s1TgRqOrUeLqjdWEQ0=";
+      hash = "sha256-TerDujO+IkSRnHYlSbAKSP9IS7AT7XnQJsZ8D8pCoGc=";
     };
     x86_64-darwin = {
       arch = "darwin-x64";
       libc = "unknown";
-      hash = "sha256-ONnXtRxcYFuFz+rmVTg+yEKe6J/vfKahX2i6k8dQStg=";
+      hash = "sha256-gphOONWujbeCCr6dkmMRJP94Dhp1Jvp2yt+g7n1HTv0=";
     };
     aarch64-darwin = {
       arch = "darwin-arm64";
       libc = "unknown";
-      hash = "sha256-VesAcT/IF2cvJVncJoqZcAvFxw32SN70C60GLU2kmVI=";
+      hash = "sha256-JMnELVUxoU1C57Tzue3Sg6OfDFAjfCnzgDit0BWzmlo=";
     };
   };
   bcryptAttrs = bcryptHostPlatformAttrs."${stdenv.hostPlatform.system}" or
     (throw "Unsupported architecture: ${stdenv.hostPlatform.system}");
-  bcryptVersion = "5.1.0";
+  bcryptVersion = "5.1.1";
   bcryptLib = fetchurl {
     url = "https://github.com/kelektiv/node.bcrypt.js/releases/download/v${bcryptVersion}/bcrypt_lib-v${bcryptVersion}-napi-v3-${bcryptAttrs.arch}-${bcryptAttrs.libc}.tar.gz";
     inherit (bcryptAttrs) hash;
@@ -45,51 +45,61 @@ let
 in
 stdenv.mkDerivation rec {
   pname = "peertube";
-  version = "5.2.1";
+  version = "6.0.3";
 
   src = fetchFromGitHub {
     owner = "Chocobozzz";
     repo = "PeerTube";
     rev = "v${version}";
-    hash = "sha256-8JzU0JVb+JQCNiro8hPHBwkofNTUy90YkSCzTOoB+/A=";
+    hash = "sha256-Pskxfi+qqVk75hu22niLNFsToCJks1k8w8mTnXjr6jg=";
   };
 
   yarnOfflineCacheServer = fetchYarnDeps {
     yarnLock = "${src}/yarn.lock";
-    hash = "sha256-pzXH6hdDf8O6Kr12Xw0jRcnPRD2TrDGdiEfxVr3KmwY=";
-  };
-
-  yarnOfflineCacheTools = fetchYarnDeps {
-    yarnLock = "${src}/server/tools/yarn.lock";
-    hash = "sha256-maPR8OCiuNlle0JQIkZSgAqW+BrSxPwVm6CkxIrIg5k=";
+    hash = "sha256-RJX92EgEIXWB1wNFRl8FvseOqBT+7m6gs+pMyoodruk=";
   };
 
   yarnOfflineCacheClient = fetchYarnDeps {
     yarnLock = "${src}/client/yarn.lock";
-    hash = "sha256-Ejzk/VEx7YtJpsrkHcXAZnJ+yRx1VhBJGpqquHYULNU=";
+    hash = "sha256-vr9xn5NXwiUS59Kgl8olCtkMgxnI1TKQzibKbb8RNXA=";
+  };
+
+  yarnOfflineCacheAppsCli = fetchYarnDeps {
+    yarnLock = "${src}/apps/peertube-cli/yarn.lock";
+    hash = "sha256-xsB71bnaPn/9/f1KHyU3TTwx+Q+1dLjWmNK2aVJgoRY=";
   };
 
-  nativeBuildInputs = [ brotli prefetch-yarn-deps jq nodejs which yarn ];
+  yarnOfflineCacheAppsRunner = fetchYarnDeps {
+    yarnLock = "${src}/apps/peertube-runner/yarn.lock";
+    hash = "sha256-9w3aLuiLs7SU00YwuE0ixfiD77gCakXT4YeRpfsgGz0=";
+  };
+
+  outputs = [ "out" "cli" "runner" ];
+
+  nativeBuildInputs = [ brotli prefetch-yarn-deps jq which yarn ];
+
+  buildInputs = [ nodejs ];
 
   buildPhase = ''
     # Build node modules
     export HOME=$PWD
     fixup-yarn-lock ~/yarn.lock
-    fixup-yarn-lock ~/server/tools/yarn.lock
     fixup-yarn-lock ~/client/yarn.lock
+    fixup-yarn-lock ~/apps/peertube-cli/yarn.lock
+    fixup-yarn-lock ~/apps/peertube-runner/yarn.lock
     yarn config --offline set yarn-offline-mirror $yarnOfflineCacheServer
     yarn install --offline --frozen-lockfile --ignore-engines --ignore-scripts --no-progress
-    cd ~/server/tools
-    yarn config --offline set yarn-offline-mirror $yarnOfflineCacheTools
-    yarn install --offline --frozen-lockfile --ignore-engines --ignore-scripts --no-progress
     cd ~/client
     yarn config --offline set yarn-offline-mirror $yarnOfflineCacheClient
     yarn install --offline --frozen-lockfile --ignore-engines --ignore-scripts --no-progress
+    cd ~/apps/peertube-cli
+    yarn config --offline set yarn-offline-mirror $yarnOfflineCacheAppsCli
+    yarn install --offline --frozen-lockfile --ignore-engines --ignore-scripts --no-progress
+    cd ~/apps/peertube-runner
+    yarn config --offline set yarn-offline-mirror $yarnOfflineCacheAppsRunner
+    yarn install --offline --frozen-lockfile --ignore-engines --ignore-scripts --no-progress
 
-    patchShebangs ~/node_modules
-    patchShebangs ~/server/tools/node_modules
-    patchShebangs ~/client/node_modules
-    patchShebangs ~/scripts
+    patchShebangs ~/{node_modules,client/node_modules,/apps/peertube-cli/node_modules,apps/peertube-runner/node_modules,scripts}
 
     # Fix bcrypt node module
     cd ~/node_modules/bcrypt
@@ -103,28 +113,52 @@ stdenv.mkDerivation rec {
     cd ~
 
     # Build PeerTube server
-    npm run tsc -- --build ./tsconfig.json
-    npm run resolve-tspaths:server
-    cp -r "./server/static" "./server/assets" "./dist/server"
-    cp -r "./server/lib/emails" "./dist/server/lib"
-
-    # Build PeerTube tools
-    cp -r "./server/tools/node_modules" "./dist/server/tools"
-    npm run tsc -- --build ./server/tools/tsconfig.json
-    npm run resolve-tspaths:server
+    npm run build:server
 
     # Build PeerTube client
     npm run build:client
+
+    # Build PeerTube cli
+    npm run build:peertube-cli
+    patchShebangs ~/apps/peertube-cli/dist/peertube.js
+
+    # Build PeerTube runner
+    npm run build:peertube-runner
+    patchShebangs ~/apps/peertube-runner/dist/peertube-runner.js
+
+    # Clean up declaration files
+    find ~/dist/ \
+      ~/packages/core-utils/dist/ \
+      ~/packages/ffmpeg/dist/ \
+      ~/packages/models/dist/ \
+      ~/packages/node-utils/dist/ \
+      ~/packages/server-commands/dist/ \
+      ~/packages/typescript-utils/dist/ \
+      \( -name '*.d.ts' -o -name '*.d.ts.map' \) -type f -delete
   '';
 
   installPhase = ''
     mkdir -p $out/dist
     mv ~/dist $out
     mv ~/node_modules $out/node_modules
-    mv ~/server/tools/node_modules $out/dist/server/tools/node_modules
     mkdir $out/client
     mv ~/client/{dist,node_modules,package.json,yarn.lock} $out/client
-    mv ~/{config,scripts,support,CREDITS.md,FAQ.md,LICENSE,README.md,package.json,tsconfig.json,yarn.lock} $out
+    mkdir -p $out/packages/{core-utils,ffmpeg,models,node-utils,server-commands,typescript-utils}
+    mv ~/packages/core-utils/{dist,package.json} $out/packages/core-utils
+    mv ~/packages/ffmpeg/{dist,package.json} $out/packages/ffmpeg
+    mv ~/packages/models/{dist,package.json} $out/packages/models
+    mv ~/packages/node-utils/{dist,package.json} $out/packages/node-utils
+    mv ~/packages/server-commands/{dist,package.json} $out/packages/server-commands
+    mv ~/packages/typescript-utils/{dist,package.json} $out/packages/typescript-utils
+    mv ~/{config,support,CREDITS.md,FAQ.md,LICENSE,README.md,package.json,yarn.lock} $out
+
+    mkdir -p $cli/bin
+    mv ~/apps/peertube-cli/{dist,node_modules,package.json,yarn.lock} $cli
+    ln -s $cli/dist/peertube.js $cli/bin/peertube-cli
+
+    mkdir -p $runner/bin
+    mv ~/apps/peertube-runner/{dist,node_modules,package.json,yarn.lock} $runner
+    ln -s $runner/dist/peertube-runner.js $runner/bin/peertube-runner
 
     # Create static gzip and brotli files
     find $out/client/dist -type f -regextype posix-extended -iregex '.*\.(css|eot|html|js|json|svg|webmanifest|xlf)' | while read file; do