]> Git — Sourcephile - julm/julm-nix.git/commitdiff
nix: update to nixos-25.05 main
authorJulien Moutinho <julm+julm-nix@sourcephile.fr>
Tue, 3 Jun 2025 01:57:23 +0000 (03:57 +0200)
committerJulien Moutinho <julm+julm-nix@sourcephile.fr>
Tue, 3 Jun 2025 01:57:23 +0000 (03:57 +0200)
22 files changed:
flake.lock
flake.nix
home-manager/profiles/developing.nix
home-manager/profiles/emacs.nix
home-manager/profiles/essential.nix
home-manager/profiles/gnupg.nix
home-manager/profiles/lang-cmn.nix
home-manager/profiles/urxvt.nix
home-manager/profiles/xmonad/xmonad.hs
homes/julm/hosts/blackberry.nix
homes/julm/hosts/oignon.nix
homes/julm/hosts/pumpkin.nix
nixos/profiles/pipewire.nix
nixpkgs/overlays.nix
nixpkgs/overlays/gcompris.nix
nixpkgs/overlays/irssi/irssi-connection-set-key.patch
nixpkgs/patches.nix
nixpkgs/patches/openvpn/0001-nixos-netns-init-module-to-manage-network-namespaces.patch
nixpkgs/patches/openvpn/0002-nixos-openvpn-add-netns-support.patch
nixpkgs/patches/syncoid/0001-nixos-sanoid-add-utilities-useful-to-syncoid.patch [new file with mode: 0644]
nixpkgs/patches/syncoid/0002-nixos-syncoid-use-DynamicUser.patch [new file with mode: 0644]
shell.nix

index 2301ad20f40aa6705d76afb5c3fd43d27262ea77..e4a2cc2e62d975ee948de8bfa435a3db2c38253e 100644 (file)
         ]
       },
       "locked": {
         ]
       },
       "locked": {
-        "lastModified": 1739757849,
-        "narHash": "sha256-Gs076ot1YuAAsYVcyidLKUMIc4ooOaRGO0PqTY7sBzA=",
+        "lastModified": 1748665073,
+        "narHash": "sha256-RMhjnPKWtCoIIHiuR9QKD7xfsKb3agxzMfJY8V9MOew=",
         "owner": "nix-community",
         "repo": "home-manager",
         "owner": "nix-community",
         "repo": "home-manager",
-        "rev": "9d3d080aec2a35e05a15cedd281c2384767c2cfe",
+        "rev": "282e1e029cb6ab4811114fc85110613d72771dea",
         "type": "github"
       },
       "original": {
         "owner": "nix-community",
         "type": "github"
       },
       "original": {
         "owner": "nix-community",
-        "ref": "release-24.11",
+        "ref": "release-25.05",
         "repo": "home-manager",
         "type": "github"
       }
         "repo": "home-manager",
         "type": "github"
       }
     },
     "nixpkgs": {
       "locked": {
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1738843498,
-        "narHash": "sha256-7x+Q4xgFj9UxZZO9aUDCR8h4vyYut4zPUvfj3i+jBHE=",
+        "lastModified": 1748263217,
+        "narHash": "sha256-iWS3iPlQhVn525rbB30BcAA5RZ9lrGcUMKqylAT/T14=",
         "owner": "NixOS",
         "repo": "nixpkgs",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "f5a32fa27df91dfc4b762671a0e0a859a8a0058f",
+        "rev": "f34483be5ee2418a563545a56743b7b59c549935",
         "type": "github"
       },
       "original": {
         "owner": "NixOS",
         "type": "github"
       },
       "original": {
         "owner": "NixOS",
-        "ref": "nixos-24.11",
         "repo": "nixpkgs",
         "repo": "nixpkgs",
+        "rev": "f34483be5ee2418a563545a56743b7b59c549935",
         "type": "github"
       }
     },
         "type": "github"
       }
     },
index b670b8a547650b3806a9f99026f4a9d726e4bb74..392ae9f69a59c4fe13b8ad35954a0a14b2eec071 100644 (file)
--- a/flake.nix
+++ b/flake.nix
     git-hooks.inputs.nixpkgs.follows = "nixpkgs";
     git-hooks.url = "github:cachix/git-hooks.nix";
     home-manager.inputs.nixpkgs.follows = "nixpkgs";
     git-hooks.inputs.nixpkgs.follows = "nixpkgs";
     git-hooks.url = "github:cachix/git-hooks.nix";
     home-manager.inputs.nixpkgs.follows = "nixpkgs";
-    home-manager.url = "github:nix-community/home-manager/release-24.11";
+    home-manager.url = "github:nix-community/home-manager/release-25.05";
     nixos-hardware.url = "github:NixOS/nixos-hardware/master";
     nixos-hardware.url = "github:NixOS/nixos-hardware/master";
-    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
+    #nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
+    nixpkgs.url = "github:NixOS/nixpkgs/f34483be5ee2418a563545a56743b7b59c549935";
     nixpkgs-unstable.url = "github:NixOS/nixpkgs/423d2df5b04b4ee7688c3d71396e872afa236a89";
     lanzaboote = {
       url = "github:nix-community/lanzaboote/v0.4.2";
     nixpkgs-unstable.url = "github:NixOS/nixpkgs/423d2df5b04b4ee7688c3d71396e872afa236a89";
     lanzaboote = {
       url = "github:nix-community/lanzaboote/v0.4.2";
     let
       remoteNixpkgsPatches = import nixpkgs/patches.nix;
       localNixpkgsPatches = [
     let
       remoteNixpkgsPatches = import nixpkgs/patches.nix;
       localNixpkgsPatches = [
+        nixpkgs/patches/openvpn/0001-nixos-netns-init-module-to-manage-network-namespaces.patch
+        nixpkgs/patches/openvpn/0002-nixos-openvpn-add-netns-support.patch
+        nixpkgs/patches/syncoid/0001-nixos-sanoid-add-utilities-useful-to-syncoid.patch
+        nixpkgs/patches/syncoid/0002-nixos-syncoid-use-DynamicUser.patch
       ];
       originPkgs = inputs.nixpkgs.legacyPackages."x86_64-linux";
       nixpkgsPath = originPkgs.applyPatches {
       ];
       originPkgs = inputs.nixpkgs.legacyPackages."x86_64-linux";
       nixpkgsPath = originPkgs.applyPatches {
index b7aa0119442e898f72d514dfa44388ab2c9ad87f..c421b338567aee40a24e68a51255ad2b76d51cb5 100644 (file)
@@ -46,7 +46,7 @@
     #pkgs.ubootTools # currently broken
     #pkgs.unar # currently broken
     pkgs.unzip
     #pkgs.ubootTools # currently broken
     #pkgs.unar # currently broken
     pkgs.unzip
-    pkgs.vbetool
+    #pkgs.vbetool
     pkgs.wgetpaste
     pkgs.xmlstarlet
     pkgs.xsel
     pkgs.wgetpaste
     pkgs.xmlstarlet
     pkgs.xsel
index 7fb75bf92902df52778930f681174d720c658a3c..93e8ad9e3a5fb540fcd05c3f2aebe42a324bfb02 100644 (file)
     sqlite
     #editorconfig-core-c
     #emacs-all-the-icons-fonts
     sqlite
     #editorconfig-core-c
     #emacs-all-the-icons-fonts
-    (nerdfonts.override {
-      fonts = [
-        "NerdFontsSymbolsOnly"
-      ];
-    })
+    pkgs.nerd-fonts.symbols-only
   ];
   home.sessionPath = [ "${config.xdg.configHome}/emacs/bin" ];
   home.sessionVariables = {
   ];
   home.sessionPath = [ "${config.xdg.configHome}/emacs/bin" ];
   home.sessionVariables = {
index 894abfda44f76ddf8aaee51d681c7d51e65ee1a9..47fce9d15cb106dc0ccbc0348e303174f67ad229 100644 (file)
@@ -23,7 +23,7 @@
       pkgs.direnv
       pkgs.dislocker
       pkgs.dmidecode
       pkgs.direnv
       pkgs.dislocker
       pkgs.dmidecode
-      pkgs.dstat
+      pkgs.dool
       pkgs.dust
       pkgs.e2fsprogs
       pkgs.efitools
       pkgs.dust
       pkgs.e2fsprogs
       pkgs.efitools
index b25e60a658862810245c97942df862edfff56ccb..c6282d3d16267cd3b753f8e47460292a71613437 100644 (file)
@@ -15,7 +15,7 @@
     enable = true;
     enableSshSupport = true;
     enableExtraSocket = true;
     enable = true;
     enableSshSupport = true;
     enableExtraSocket = true;
-    pinentryPackage = lib.mkDefault (
+    pinentry.package = lib.mkDefault (
       if nixosConfig.services.xserver.enable then pkgs.pinentry-gtk2 else pkgs.pinentry-curses
     );
   };
       if nixosConfig.services.xserver.enable then pkgs.pinentry-gtk2 else pkgs.pinentry-curses
     );
   };
index b5e635df71ba01cc7824e92979811aaa1e21c0e8..a968dea8843bb1fa5463c064a5221bc924f3ac58 100644 (file)
@@ -1,9 +1,11 @@
 { config, pkgs, ... }:
 {
   i18n.inputMethod = {
 { config, pkgs, ... }:
 {
   i18n.inputMethod = {
-    enabled = "fcitx5";
+    type = "fcitx5";
+    enable = true;
     fcitx5.addons = [
     fcitx5.addons = [
-      # Be sure to select « rime » and not « chinese » in fcitx5-configtool
+      # WarningNote: be sure to select « rime »
+      # and not « chinese » in fcitx5-configtool
       pkgs.fcitx5-rime
       pkgs.fcitx5-gtk
       pkgs.libsForQt5.fcitx5-chinese-addons
       pkgs.fcitx5-rime
       pkgs.fcitx5-gtk
       pkgs.libsForQt5.fcitx5-chinese-addons
index 7b6bdc7a2a8c6f8fc36d5b7465bcf2e767f2e13c..d8c4a4f37de859435b864b0f7f4a395154b2f9c8 100644 (file)
@@ -2,12 +2,8 @@
 {
   home.packages = [
     pkgs.rxvt-unicode
 {
   home.packages = [
     pkgs.rxvt-unicode
-    (pkgs.nerdfonts.override {
-      fonts = [
-        #"DejaVuSansMono"
-        "NerdFontsSymbolsOnly"
-      ];
-    })
+    pkgs.nerd-fonts.symbols-only
+    #pkgs.nerd-fonts.DejaVuSansMono
   ];
   systemd.user.services.urxvt = {
     Unit = {
   ];
   systemd.user.services.urxvt = {
     Unit = {
index b562527000801e5cac082d7cc071f099ec9963bf..47da9ebbeb7730e1900ac3fa5850a293a5ae3f34 100644 (file)
@@ -337,7 +337,7 @@ main = xmonad $
 
 spawnCommand = spawnExec "rofi -show run -no-disable-history -run-command \"bash -c 'systemd-run --user --unit=app-org.rofi.\\$(systemd-escape \\\"{cmd}\\\")@\\$RANDOM -p CollectMode=inactive-or-failed {cmd}'\""
 
 
 spawnCommand = spawnExec "rofi -show run -no-disable-history -run-command \"bash -c 'systemd-run --user --unit=app-org.rofi.\\$(systemd-escape \\\"{cmd}\\\")@\\$RANDOM -p CollectMode=inactive-or-failed {cmd}'\""
 
-barSpawner :: ScreenId -> IO StatusBarConfig
+barSpawner :: ScreenId -> X StatusBarConfig
 barSpawner 0 = pure $ topXmobar <> traySB
 --barSpawner 1 = pure $ xmobar1
 barSpawner _ = pure $ topXmobar -- nothing on the rest of the screens
 barSpawner 0 = pure $ topXmobar <> traySB
 --barSpawner 1 = pure $ xmobar1
 barSpawner _ = pure $ topXmobar -- nothing on the rest of the screens
index a4b1624c8274e9a5d6ea606a7082f3857e90224e..8e16021061147edb1c0495e3927734c3368a7400 100644 (file)
@@ -51,7 +51,7 @@
     pkgs.calibre
     #pkgs.zotero
     pkgs.evince
     pkgs.calibre
     #pkgs.zotero
     pkgs.evince
-    pkgs.marble
+    pkgs.kdePackages.marble
     pkgs.gcompris
     pkgs.frozen-bubble
     pkgs.neverball
     pkgs.gcompris
     pkgs.frozen-bubble
     pkgs.neverball
index fb1fd3b390247ac80d11e547f1dfee23b50fdd55..49ebd23685ae8459c17526eb60cc611999026f76 100644 (file)
@@ -58,7 +58,7 @@
     pkgs.calibre
     pkgs.zotero
     pkgs.evince
     pkgs.calibre
     pkgs.zotero
     pkgs.evince
-    pkgs.marble
+    pkgs.kdePackages.marble
     pkgs.gcompris
     pkgs.frozen-bubble
     pkgs.neverball
     pkgs.gcompris
     pkgs.frozen-bubble
     pkgs.neverball
index 2a118dcab4335cf7e2921a2e448da7bb7aedd711..1abd2750c139b3970c0ea5a330080a242f04d97a 100644 (file)
@@ -60,7 +60,7 @@
     pkgs.pdftk
     pkgs.vips
     pkgs.poppler_utils
     pkgs.pdftk
     pkgs.vips
     pkgs.poppler_utils
-    # psnup conflicts with pkgs.texlive.combined.scheme-*
+    # ExplanationNote: psnup conflicts with pkgs.texlive.combined.scheme-*
     (lib.lowPrio pkgs.psutils)
     pkgs.ink
     pkgs.djview
     (lib.lowPrio pkgs.psutils)
     pkgs.ink
     pkgs.djview
     pkgs.calibre
     pkgs.zotero
     pkgs.evince
     pkgs.calibre
     pkgs.zotero
     pkgs.evince
-    pkgs.marble
+    pkgs.kdePackages.marble
     pkgs.gcompris
     pkgs.frozen-bubble
     pkgs.neverball
     pkgs.tuxpaint
     pkgs.rmg
     pkgs.gcompris
     pkgs.frozen-bubble
     pkgs.neverball
     pkgs.tuxpaint
     pkgs.rmg
-    pkgs.veloren
+    #pkgs.veloren
     pkgs.shipwright
     pkgs.steam-run
     pkgs.xsane
     pkgs.shipwright
     pkgs.steam-run
     pkgs.xsane
index 7ddaf6eef44a6204e1500b94d879ea535e96ed9a..63ca1d68c475ed09f55874f8e4d61bea770a7c17 100644 (file)
@@ -12,7 +12,7 @@ with lib;
 
   # rtkit is optional but recommended
   security.rtkit.enable = mkDefault config.services.pipewire.enable;
 
   # rtkit is optional but recommended
   security.rtkit.enable = mkDefault config.services.pipewire.enable;
-  hardware.pulseaudio.enable = false;
+  services.pulseaudio.enable = false;
   services.pipewire = {
     enable = true;
     alsa.enable = mkDefault true;
   services.pipewire = {
     enable = true;
     alsa.enable = mkDefault true;
index 05f9ad965154a4f256c9b9f0b71fc5ea77853790..86837041843077db5ac0814cf3fd4095cb141e34 100644 (file)
@@ -1,6 +1,6 @@
 map import [
   overlays/anydesk.nix
 map import [
   overlays/anydesk.nix
-  overlays/gcompris.nix
+  overlays/gcompris.nix
   overlays/git-remote-gpg.nix
   overlays/irssi.nix
   overlays/modemmanager.nix
   overlays/git-remote-gpg.nix
   overlays/irssi.nix
   overlays/modemmanager.nix
index fc983600f6931c53c7d5fac47dac714c1b6e16c1..7fe645c8e80999af8cf6b724a9a836578fb39a49 100644 (file)
@@ -1,5 +1,5 @@
 finalPkgs: previousPkgs: {
   gcompris = previousPkgs.gcompris.overrideAttrs (previousAttrs: rec {
 finalPkgs: previousPkgs: {
   gcompris = previousPkgs.gcompris.overrideAttrs (previousAttrs: rec {
-    buildInputs = previousAttrs.buildInputs ++ [ finalPkgs.qt5.qtimageformats ];
+    buildInputs = previousAttrs.buildInputs ++ [ finalPkgs.qt6.qtimageformats ];
   });
 }
   });
 }
index b32ce97a2d658006d232036adf38b4fa5ab8d161..ba273cc4a800f5b87ca1cfa0e0197d1a60f69db0 100644 (file)
@@ -56,6 +56,20 @@ index 30fc684..83733d0 100644
 +      return TRUE;
 +}
 +
 +      return TRUE;
 +}
 +
+diff --git i/src/core/servers.h w/src/core/servers.h
+index d52603e..05fb2e5 100644
+--- i/src/core/servers.h
++++ w/src/core/servers.h
+@@ -66,6 +66,9 @@ void server_connect_failed(SERVER_REC *server, const char *msg);
+ /* Change your nick */
+ void server_change_nick(SERVER_REC *server, const char *nick);
++/* Set a key via passwd.pl */
++int server_connection_set_key(SERVER_REC *server, char *key, char *value);
++
+ /* Push meta data onto the server stash */
+ void server_meta_stash(SERVER_REC *server, const char *meta_key, const char *meta_value);
+ /* Get a value from the stash */
 diff --git a/src/perl/common/Server.xs b/src/perl/common/Server.xs
 index 60878a6..1b6d6d1 100644
 --- a/src/perl/common/Server.xs
 diff --git a/src/perl/common/Server.xs b/src/perl/common/Server.xs
 index 60878a6..1b6d6d1 100644
 --- a/src/perl/common/Server.xs
index aeae4ee4e0f9b0b0e569f54bfc14c0a361b78210..f93da9062ca62c5235996d0d190a1115d5af063c 100644 (file)
@@ -1,14 +1,19 @@
 [
 [
-  {
-    meta.description = "openvpn";
-    url = "https://github.com/NixOS/nixpkgs/pull/109643.diff";
-    sha256 = "sha256-D/FKkaNdStNZPbF5jeptw+yUIkQYF6umUDIUJCOhlyA=";
-  }
-  {
-    meta.description = "veloren";
-    url = "https://github.com/NixOS/nixpkgs/pull/368944.diff";
-    sha256 = "sha256-4F0eQDIGAmxsryhDpLiGdW+krTeuIs31HXOtbbKncNI=";
-  }
+  /*
+    {
+      meta.description = "openvpn";
+      url = "https://github.com/NixOS/nixpkgs/pull/109643.diff";
+      sha256 = "sha256-D/FKkaNdStNZPbF5jeptw+yUIkQYF6umUDIUJCOhlyA=";
+    }
+  */
+  /*
+    Fail to patch nixos-24.11
+    {
+      meta.description = "ffmpeg-full";
+      url = "https://github.com/NixOS/nixpkgs/pull/379413.diff";
+      sha256 = "sha256-SfHTcK1pUHHhl43uGE7Jfv+/8730qbc2n+egIef3v0Q=";
+    }
+  */
   /*
     No longer mergeable into nixos-24.11, wait for nixos-25.05
     {
   /*
     No longer mergeable into nixos-24.11, wait for nixos-25.05
     {
       sha256 = "sha256-RionFgtSi3+SWrktMw9qrG4p+XRk2OhzwN8COr+UFnU=";
     }
   */
       sha256 = "sha256-RionFgtSi3+SWrktMw9qrG4p+XRk2OhzwN8COr+UFnU=";
     }
   */
-  {
-    meta.description = "nixos/syncoid: enable N:N dataset mappings";
-    url = "https://github.com/NixOS/nixpkgs/pull/236729.diff";
-    sha256 = "sha256-M2t0CF1+6Zx11ciW70L7KF1Pogchj1dObhTp8zlkpKM=";
-  }
+  /*
+    {
+      meta.description = "nixos/syncoid: enable N:N dataset mappings";
+      url = "https://github.com/NixOS/nixpkgs/pull/236729.diff";
+      sha256 = "sha256-M2t0CF1+6Zx11ciW70L7KF1Pogchj1dObhTp8zlkpKM=";
+    }
+  */
 ]
 ]
index 8b6b9f2418e78d4477cb59a8a5d23d18abdbd012..c2fcabef2064bfc74c90a6a574f4367448e600b5 100644 (file)
@@ -1,19 +1,19 @@
-From 3afee8c8b5309d15528a2d132e601b5422182ef5 Mon Sep 17 00:00:00 2001
+From 6805353e349165acb54e59abff10c53ea9069da7 Mon Sep 17 00:00:00 2001
 From: Julien Moutinho <julm+nixpkgs@sourcephile.fr>
 Date: Sun, 17 Jan 2021 15:28:13 +0100
 Subject: [PATCH 1/2] nixos/netns: init module to manage network namespaces
 
 ---
  nixos/modules/module-list.nix               |   1 +
 From: Julien Moutinho <julm+nixpkgs@sourcephile.fr>
 Date: Sun, 17 Jan 2021 15:28:13 +0100
 Subject: [PATCH 1/2] nixos/netns: init module to manage network namespaces
 
 ---
  nixos/modules/module-list.nix               |   1 +
- nixos/modules/services/networking/netns.nix | 112 ++++++++++++++++++++
- 2 files changed, 113 insertions(+)
+ nixos/modules/services/networking/netns.nix | 133 ++++++++++++++++++++
+ 2 files changed, 134 insertions(+)
  create mode 100644 nixos/modules/services/networking/netns.nix
 
 diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
  create mode 100644 nixos/modules/services/networking/netns.nix
 
 diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
-index 783f20546af4..3df0004f5f82 100644
+index 79f5c22f5b..a350346754 100644
 --- a/nixos/modules/module-list.nix
 +++ b/nixos/modules/module-list.nix
 --- a/nixos/modules/module-list.nix
 +++ b/nixos/modules/module-list.nix
-@@ -1161,6 +1161,7 @@
+@@ -1235,6 +1235,7 @@
    ./services/networking/netclient.nix
    ./services/networking/networkd-dispatcher.nix
    ./services/networking/networkmanager.nix
    ./services/networking/netclient.nix
    ./services/networking/networkd-dispatcher.nix
    ./services/networking/networkmanager.nix
@@ -23,122 +23,143 @@ index 783f20546af4..3df0004f5f82 100644
    ./services/networking/nghttpx/default.nix
 diff --git a/nixos/modules/services/networking/netns.nix b/nixos/modules/services/networking/netns.nix
 new file mode 100644
    ./services/networking/nghttpx/default.nix
 diff --git a/nixos/modules/services/networking/netns.nix b/nixos/modules/services/networking/netns.nix
 new file mode 100644
-index 000000000000..8e37c2226848
+index 0000000000..eb05d0c0fd
 --- /dev/null
 +++ b/nixos/modules/services/networking/netns.nix
 --- /dev/null
 +++ b/nixos/modules/services/networking/netns.nix
-@@ -0,0 +1,112 @@
-+{ pkgs, lib, config, options, ... }:
+@@ -0,0 +1,133 @@
++{
++  pkgs,
++  lib,
++  config,
++  options,
++  ...
++}:
 +with lib;
 +let
 +  cfg = config.services.netns;
 +  inherit (config) networking;
 +  # Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
 +with lib;
 +let
 +  cfg = config.services.netns;
 +  inherit (config) networking;
 +  # Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
-+  escapeUnitName = name:
-+    lib.concatMapStrings (s: if lib.isList s then "-" else s)
-+    (builtins.split "[^a-zA-Z0-9_.\\-]+" name);
++  escapeUnitName =
++    name:
++    lib.concatMapStrings (s: if lib.isList s then "-" else s) (
++      builtins.split "[^a-zA-Z0-9_.\\-]+" name
++    );
 +in
 +{
 +in
 +{
-+options.services.netns = {
-+  namespaces = mkOption {
-+    description = ''
-+      Network namespaces to create.
++  options.services.netns = {
++    namespaces = mkOption {
++      description = ''
++        Network namespaces to create.
 +
 +
-+      Other services can join a network namespace named `netns` with:
-+      ```
-+      PrivateNetwork=true;
-+      JoinsNamespaceOf="netns-''${netns}.service";
-+      ```
++        Other services can join a network namespace named `netns` with:
++        ```
++        PrivateNetwork=true;
++        JoinsNamespaceOf="netns-''${netns}.service";
++        ```
 +
 +
-+      So can `iproute` with: `ip -n ''${netns}`
++        So can `iproute2` with: `ip -n ''${netns}`
 +
 +
-+      ::: {.warning}
-+      You should usually create (or update via your VPN configuration's up script)
-+      a file named `/etc/netns/''${netns}/resolv.conf`
-+      that will be bind-mounted by `ip -n ''${netns}` onto `/etc/resolv.conf`,
-+      which you'll also want to configure in the services joining this network namespace:
-+      ```
-+      BindReadOnlyPaths = ["/etc/netns/''${netns}/resolv.conf:/etc/resolv.conf"];
-+      ```
-+      :::
-+    '';
-+    default = {};
-+    type = types.attrsOf (types.submodule {
-+      options.nftables = mkOption {
-+        description = "Nftables ruleset within the network namespace.";
-+        type = types.lines;
-+        default = networking.nftables.ruleset;
-+        defaultText = "config.networking.nftables.ruleset";
-+      };
-+      options.sysctl = options.boot.kernel.sysctl // {
-+        description = "sysctl within the network namespace.";
-+        default = config.boot.kernel.sysctl;
-+        defaultText = literalMD "config.boot.kernel.sysctl";
-+      };
-+      options.service = mkOption {
-+        description = "Systemd configuration specific to this netns service";
-+        type = types.attrs;
-+        default = {};
-+      };
-+    });
++        ::: {.warning}
++        You should usually create (or update via your VPN configuration's up script)
++        a file named `/etc/netns/''${netns}/resolv.conf`
++        that will be bind-mounted by `ip -n ''${netns}` onto `/etc/resolv.conf`,
++        which you'll also want to configure in the services joining this network namespace:
++        ```
++        BindReadOnlyPaths = ["/etc/netns/''${netns}/resolv.conf:/etc/resolv.conf"];
++        ```
++        :::
++      '';
++      default = { };
++      type = types.attrsOf (
++        types.submodule {
++          options.nftables = mkOption {
++            description = "Nftables ruleset within the network namespace.";
++            type = types.lines;
++            default = networking.nftables.ruleset;
++            defaultText = "config.networking.nftables.ruleset";
++          };
++          options.sysctl = options.boot.kernel.sysctl // {
++            description = "sysctl within the network namespace.";
++            default = config.boot.kernel.sysctl;
++            defaultText = literalMD "config.boot.kernel.sysctl";
++          };
++          options.service = mkOption {
++            description = "Systemd configuration specific to this netns service";
++            type = types.attrs;
++            default = { };
++          };
++        }
++      );
++    };
 +  };
 +  };
-+};
-+config = {
-+  systemd.services = mapAttrs' (name: c:
-+    nameValuePair "netns-${escapeUnitName name}" (mkMerge [
-+      { description = "${name} network namespace";
-+        before = [ "network.target" ];
-+        serviceConfig = {
-+          Type = "oneshot";
-+          RemainAfterExit = true;
-+          # Let systemd create the netns so that PrivateNetwork=true
-+          # with JoinsNamespaceOf="netns-${name}.service" works.
-+          PrivateNetwork = true;
-+          # PrivateNetwork=true implies PrivateMounts=true by default,
-+          # which would prevent the persisting and sharing of /var/run/netns/$name
-+          # causing `ip netns exec $name $SHELL` outside of this service to fail with:
-+          # Error: Peer netns reference is invalid.
-+          # As `stat -f -c %T /var/run/netns/$name` would not be "nsfs" in those mntns.
-+          # See https://serverfault.com/questions/961504/cannot-create-nested-network-namespace
-+          PrivateMounts = false;
-+          ExecStart = [
-+            # Register the netns with a binding mount to /var/run/netns/$name to keep it alive,
-+            # and make sure resolv.conf can be used in BindReadOnlyPaths=
-+            # For propagating changes in that file to the services bind mounting it,
-+            # updating must not remove the file, but only truncate it.
-+            (pkgs.writeShellScript "ip-netns-attach" ''
-+              ${pkgs.iproute}/bin/ip netns attach ${escapeShellArg name} $$
-+              mkdir -p /etc/netns/${escapeShellArg name}
-+              touch /etc/netns/${escapeShellArg name}/resolv.conf
-+            '')
++  config = {
++    systemd.services = mapAttrs' (
++      name: c:
++      nameValuePair "netns-${escapeUnitName name}" (mkMerge [
++        {
++          description = "${name} network namespace";
++          before = [ "network.target" ];
++          serviceConfig = {
++            Type = "oneshot";
++            RemainAfterExit = true;
++            # Let systemd create the netns so that PrivateNetwork=true
++            # with JoinsNamespaceOf="netns-${name}.service" works.
++            PrivateNetwork = true;
++            # PrivateNetwork=true implies PrivateMounts=true by default,
++            # which would prevent the persisting and sharing of /var/run/netns/$name
++            # causing `ip netns exec $name $SHELL` outside of this service to fail with:
++            # Error: Peer netns reference is invalid.
++            # As `stat -f -c %T /var/run/netns/$name` would not be "nsfs" in those mntns.
++            # See https://serverfault.com/questions/961504/cannot-create-nested-network-namespace
++            PrivateMounts = false;
++            ExecStart =
++              [
++                # Register the netns with a binding mount to /var/run/netns/$name to keep it alive,
++                # and make sure resolv.conf can be used in BindReadOnlyPaths=
++                # For propagating changes in that file to the services bind mounting it,
++                # updating must not remove the file, but only truncate it.
++                (pkgs.writeShellScript "ip-netns-attach" ''
++                  ${pkgs.iproute2}/bin/ip netns attach ${escapeShellArg name} $$
++                  mkdir -p /etc/netns/${escapeShellArg name}
++                  touch /etc/netns/${escapeShellArg name}/resolv.conf
++                '')
 +
 +
-+            # Bringing the loopback interface is almost always a good thing.
-+            "${pkgs.iproute}/bin/ip link set dev lo up"
++                # Bringing the loopback interface is almost always a good thing.
++                "${pkgs.iproute2}/bin/ip link set dev lo up"
 +
 +
-+            # Use --ignore because some keys may no longer exist in that new namespace,
-+            # like net.ipv6.conf.eth0.addr_gen_mode or net.core.rmem_max
-+            ''${pkgs.procps}/bin/sysctl --ignore -p ${pkgs.writeScript "sysctl"
-+              (concatStrings (mapAttrsToList (n: v:
-+                optionalString (v != null)
-+                  "${n}=${if v == false then "0" else toString v}\n"
-+                ) c.sysctl))}
-+            ''
-+          ] ++
-+          # Load the nftables ruleset of this netns.
-+          optional networking.nftables.enable (pkgs.writeScript "nftables-ruleset" ''
-+            #!${pkgs.nftables}/bin/nft -f
-+            flush ruleset
-+            ${c.nftables}
-+          '');
-+          # Unregister the netns from the tracking mecanism of iproute.
-+          ExecStop = "${pkgs.iproute}/bin/ip netns delete ${escapeShellArg name}";
-+        };
-+      }
-+      c.service
-+    ]
-+    )) cfg.namespaces;
-+  meta.maintainers = with lib.maintainers; [ julm ];
-+};
++                # Use --ignore because some keys may no longer exist in that new namespace,
++                # like net.ipv6.conf.eth0.addr_gen_mode or net.core.rmem_max
++                ''
++                  ${pkgs.procps}/bin/sysctl --ignore -p ${
++                    pkgs.writeScript "sysctl" (
++                      concatStrings (
++                        mapAttrsToList (
++                          n: v: optionalString (v != null) "${n}=${if v == false then "0" else toString v}\n"
++                        ) c.sysctl
++                      )
++                    )
++                  }
++                ''
++              ]
++              ++
++              # Load the nftables ruleset of this netns.
++              optional networking.nftables.enable (
++                pkgs.writeScript "nftables-ruleset" ''
++                  #!${pkgs.nftables}/bin/nft -f
++                  flush ruleset
++                  ${c.nftables}
++                ''
++              );
++            # Unregister the netns from the tracking mecanism of iproute2.
++            ExecStop = "${pkgs.iproute2}/bin/ip netns delete ${escapeShellArg name}";
++          };
++        }
++        c.service
++      ])
++    ) cfg.namespaces;
++    meta.maintainers = with lib.maintainers; [ julm ];
++  };
 +}
 -- 
 +}
 -- 
-2.44.1
+2.47.2
 
 
index fe933f793c2b1886f6a8bc640d10c89350b38e28..0be1bbab1887271db0084d5792fda2b4a553837d 100644 (file)
@@ -1,17 +1,17 @@
-From 600839a7759be481904d029798e563e23df930db Mon Sep 17 00:00:00 2001
+From 2ae3f92dcd1ffe66ddd1bb87627d9c08756df39b Mon Sep 17 00:00:00 2001
 From: Julien Moutinho <julm+nixpkgs@sourcephile.fr>
 Date: Sun, 17 Jan 2021 15:28:25 +0100
 Subject: [PATCH 2/2] nixos/openvpn: add netns support
 
 ---
 From: Julien Moutinho <julm+nixpkgs@sourcephile.fr>
 Date: Sun, 17 Jan 2021 15:28:25 +0100
 Subject: [PATCH 2/2] nixos/openvpn: add netns support
 
 ---
- nixos/modules/services/networking/openvpn.nix | 395 ++++++++++++++----
- 1 file changed, 314 insertions(+), 81 deletions(-)
+ nixos/modules/services/networking/openvpn.nix | 509 +++++++++++++-----
+ 1 file changed, 382 insertions(+), 127 deletions(-)
 
 diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
 
 diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
-index 56b1f6f5ab8f..9805099789b1 100644
+index 0231e43447..d0a95e6e5a 100644
 --- a/nixos/modules/services/networking/openvpn.nix
 +++ b/nixos/modules/services/networking/openvpn.nix
 --- a/nixos/modules/services/networking/openvpn.nix
 +++ b/nixos/modules/services/networking/openvpn.nix
-@@ -5,55 +5,205 @@ with lib;
+@@ -10,56 +10,210 @@ with lib;
  let
  
    cfg = config.services.openvpn;
  let
  
    cfg = config.services.openvpn;
@@ -21,31 +21,33 @@ index 56b1f6f5ab8f..9805099789b1 100644
  
 +  PATH = name: makeBinPath config.systemd.services."openvpn-${name}".path;
 +
  
 +  PATH = name: makeBinPath config.systemd.services."openvpn-${name}".path;
 +
-   makeOpenVPNJob = cfg: name:
+   makeOpenVPNJob =
+     cfg: name:
      let
  
 -      path = makeBinPath (getAttr "openvpn-${name}" config.systemd.services).path;
 +      configFile = pkgs.writeText "openvpn-config-${name}" (
      let
  
 -      path = makeBinPath (getAttr "openvpn-${name}" config.systemd.services).path;
 +      configFile = pkgs.writeText "openvpn-config-${name}" (
-+        generators.toKeyValue
-+          {
-+            mkKeyValue = key: value:
-+              if hasAttr key scripts
-+              then "${key} " + pkgs.writeShellScript "openvpn-${name}-${key}" (scripts.${key} value)
-+              else if builtins.isBool value
-+              then optionalString value key
-+              else if builtins.isPath value
-+              then "${key} ${value}"
-+              else if builtins.isList value
-+              then concatMapStringsSep "\n" (v: "${key} ${generators.mkValueStringDefault {} v}") value
-+              else "${key} ${generators.mkValueStringDefault {} value}";
-+          }
-+          cfg.settings
++        generators.toKeyValue {
++          mkKeyValue =
++            key: value:
++            if hasAttr key scripts then
++              "${key} " + pkgs.writeShellScript "openvpn-${name}-${key}" (scripts.${key} value)
++            else if builtins.isBool value then
++              optionalString value key
++            else if builtins.isPath value then
++              "${key} ${value}"
++            else if builtins.isList value then
++              concatMapStringsSep "\n" (v: "${key} ${generators.mkValueStringDefault { } v}") value
++            else
++              "${key} ${generators.mkValueStringDefault { } value}";
++        } cfg.settings
 +      );
  
 -      upScript = ''
 -        export PATH=${path}
 +      scripts = {
 +      );
  
 -      upScript = ''
 -        export PATH=${path}
 +      scripts = {
-+        up = script:
++        up =
++          script:
 +          let
 +            init = ''
 +              export PATH=${PATH name}
 +          let
 +            init = ''
 +              export PATH=${PATH name}
@@ -72,11 +74,9 @@ index 56b1f6f5ab8f..9805099789b1 100644
 +              done
  
 -        ${cfg.up}
 +              done
  
 -        ${cfg.up}
--        ${optionalString cfg.updateResolvConf
--           "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
+-        ${optionalString cfg.updateResolvConf "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
 -      '';
 -      '';
-+              ${optionalString cfg.updateResolvConf
-+                 "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
++              ${optionalString cfg.updateResolvConf "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
 +            '';
 +            # Add DNS settings given in foreign DHCP options to the resolv.conf of the netns.
 +            # Note that JoinsNamespaceOf="netns-${cfg.netns}.service" will not
 +            '';
 +            # Add DNS settings given in foreign DHCP options to the resolv.conf of the netns.
 +            # Note that JoinsNamespaceOf="netns-${cfg.netns}.service" will not
@@ -110,159 +110,166 @@ index 56b1f6f5ab8f..9805099789b1 100644
 +              done
 +            '';
 +          in
 +              done
 +            '';
 +          in
-+          if cfg.netns == null
-+          then ''
-+            ${init}
-+            ${script}
-+          ''
-+          else ''
-+            export PATH=${PATH name}
-+            set -eux
-+            ${setNetNSResolvConf}
-+            ip link set dev '${cfg.settings.dev}' up netns '${cfg.netns}' mtu "$tun_mtu"
-+            ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-up-netns.sh" ''
++          if cfg.netns == null then
++            ''
 +              ${init}
 +              ${init}
-+              set -eux
++              ${script}
++            ''
++          else
++            ''
 +              export PATH=${PATH name}
 +              export PATH=${PATH name}
++              set -eux
++              ${setNetNSResolvConf}
++              ip link set dev '${cfg.settings.dev}' up netns '${cfg.netns}' mtu "$tun_mtu"
++              ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-up-netns.sh" ''
++                ${init}
++                set -eux
++                export PATH=${PATH name}
  
 -      downScript = ''
 -        export PATH=${path}
  
 -      downScript = ''
 -        export PATH=${path}
--        ${optionalString cfg.updateResolvConf
--           "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
+-        ${optionalString cfg.updateResolvConf "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
 -        ${cfg.down}
 -      '';
 -        ${cfg.down}
 -      '';
-+              ip link set dev lo up
++                ip link set dev lo up
  
  
--      configFile = pkgs.writeText "openvpn-config-${name}"
--        ''
--          errors-to-stderr
--          ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
--          ${cfg.config}
--          ${optionalString (cfg.up != "" || cfg.updateResolvConf)
--              "up ${pkgs.writeShellScript "openvpn-${name}-up" upScript}"}
--          ${optionalString (cfg.down != "" || cfg.updateResolvConf)
--              "down ${pkgs.writeShellScript "openvpn-${name}-down" downScript}"}
--          ${optionalString (cfg.authUserPass != null)
--              "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
--                ${cfg.authUserPass.username}
--                ${cfg.authUserPass.password}
--              ''}"}
--        '';
-+              netmask4="''${ifconfig_netmask:-30}"
-+              netbits6="''${ifconfig_ipv6_netbits:-112}"
-+              if [ -n "''${ifconfig_local-}" ]; then
-+                if [ -n "''${ifconfig_remote-}" ]; then
-+                  ip -4 addr replace \
-+                    local "$ifconfig_local" \
-+                    peer "$ifconfig_remote/$netmask4" \
-+                    ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
-+                    dev '${cfg.settings.dev}'
-+                else
-+                  ip -4 addr replace \
-+                    local "$ifconfig_local/$netmask4" \
-+                    ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
-+                    dev '${cfg.settings.dev}'
+-      configFile = pkgs.writeText "openvpn-config-${name}" ''
+-        errors-to-stderr
+-        ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
+-        ${cfg.config}
+-        ${optionalString (
+-          cfg.up != "" || cfg.updateResolvConf
+-        ) "up ${pkgs.writeShellScript "openvpn-${name}-up" upScript}"}
+-        ${optionalString (
+-          cfg.down != "" || cfg.updateResolvConf
+-        ) "down ${pkgs.writeShellScript "openvpn-${name}-down" downScript}"}
+-        ${optionalString (cfg.authUserPass != null)
+-          "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
+-            ${cfg.authUserPass.username}
+-            ${cfg.authUserPass.password}
+-          ''}"
+-        }
+-      '';
++                netmask4="''${ifconfig_netmask:-30}"
++                netbits6="''${ifconfig_ipv6_netbits:-112}"
++                if [ -n "''${ifconfig_local-}" ]; then
++                  if [ -n "''${ifconfig_remote-}" ]; then
++                    ip -4 addr replace \
++                      local "$ifconfig_local" \
++                      peer "$ifconfig_remote/$netmask4" \
++                      ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
++                      dev '${cfg.settings.dev}'
++                  else
++                    ip -4 addr replace \
++                      local "$ifconfig_local/$netmask4" \
++                      ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
++                      dev '${cfg.settings.dev}'
++                  fi
 +                fi
 +                fi
-+              fi
-+              if [ -n "''${ifconfig_ipv6_local-}" ]; then
-+                if [ -n "''${ifconfig_ipv6_remote-}" ]; then
-+                  ip -6 addr replace \
-+                    local "$ifconfig_ipv6_local" \
-+                    peer "$ifconfig_ipv6_remote/$netbits6" \
-+                    dev '${cfg.settings.dev}'
-+                else
-+                  ip -6 addr replace \
-+                    local "$ifconfig_ipv6_local/$netbits6" \
-+                    dev '${cfg.settings.dev}'
++                if [ -n "''${ifconfig_ipv6_local-}" ]; then
++                  if [ -n "''${ifconfig_ipv6_remote-}" ]; then
++                    ip -6 addr replace \
++                      local "$ifconfig_ipv6_local" \
++                      peer "$ifconfig_ipv6_remote/$netbits6" \
++                      dev '${cfg.settings.dev}'
++                  else
++                    ip -6 addr replace \
++                      local "$ifconfig_ipv6_local/$netbits6" \
++                      dev '${cfg.settings.dev}'
++                  fi
 +                fi
 +                fi
-+              fi
-+              set +eux
-+              ${script}
-+            ''}
-+          '';
-+        route-up = script:
-+          if cfg.netns == null
-+          then script
-+          else ''
-+            export PATH=${PATH name}
-+            set -eux
-+            ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-route-up-netns" ''
++                set +eux
++                ${script}
++              ''}
++            '';
++        route-up =
++          script:
++          if cfg.netns == null then
++            script
++          else
++            ''
 +              export PATH=${PATH name}
 +              set -eux
 +              export PATH=${PATH name}
 +              set -eux
-+              i=1
-+              while
-+                eval net=\"\''${route_network_$i-}\"
-+                eval mask=\"\''${route_netmask_$i-}\"
-+                eval gw=\"\''${route_gateway_$i-}\"
-+                eval mtr=\"\''${route_metric_$i-}\"
-+                [ -n "$net" ]
-+              do
-+                ip -4 route replace "$net/$mask" via "$gw" ''${mtr:+metric "$mtr"}
-+                i=$(( i + 1 ))
-+              done
++              ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-route-up-netns" ''
++                export PATH=${PATH name}
++                set -eux
++                i=1
++                while
++                  eval net=\"\''${route_network_$i-}\"
++                  eval mask=\"\''${route_netmask_$i-}\"
++                  eval gw=\"\''${route_gateway_$i-}\"
++                  eval mtr=\"\''${route_metric_$i-}\"
++                  [ -n "$net" ]
++                do
++                  ip -4 route replace "$net/$mask" via "$gw" ''${mtr:+metric "$mtr"}
++                  i=$(( i + 1 ))
++                done
 +
 +
-+              if [ -n "''${route_vpn_gateway-}" ]; then
-+                ip -4 route replace default via "$route_vpn_gateway"
-+              fi
++                if [ -n "''${route_vpn_gateway-}" ]; then
++                  ip -4 route replace default via "$route_vpn_gateway"
++                fi
 +
 +
-+              i=1
-+              while
-+                # There doesn't seem to be $route_ipv6_metric_<n>
-+                # according to the manpage.
-+                eval net=\"\''${route_ipv6_network_$i-}\"
-+                eval gw=\"\''${route_ipv6_gateway_$i-}\"
-+                [ -n "$net" ]
-+              do
-+                ip -6 route replace  "$net"  via "$gw"  metric 100
-+                i=$(( i + 1 ))
-+              done
++                i=1
++                while
++                  # There doesn't seem to be $route_ipv6_metric_<n>
++                  # according to the manpage.
++                  eval net=\"\''${route_ipv6_network_$i-}\"
++                  eval gw=\"\''${route_ipv6_gateway_$i-}\"
++                  [ -n "$net" ]
++                do
++                  ip -6 route replace  "$net"  via "$gw"  metric 100
++                  i=$(( i + 1 ))
++                done
 +
 +
-+              # There's no $route_vpn_gateway for IPv6. It's not
-+              # documented if OpenVPN includes default route in
-+              # $route_ipv6_*. Set default route to remote VPN
-+              # endpoint address if there is one. Use higher metric
-+              # than $route_ipv6_* routes to give preference to a
-+              # possible default route in them.
-+              if [ -n "''${ifconfig_ipv6_remote-}" ]; then
-+                ip -6 route replace default \
-+                  via "$ifconfig_ipv6_remote" metric 200
-+              fi
-+              ${script}
-+            ''}
-+          '';
-+        down = script:
++                # There's no $route_vpn_gateway for IPv6. It's not
++                # documented if OpenVPN includes default route in
++                # $route_ipv6_*. Set default route to remote VPN
++                # endpoint address if there is one. Use higher metric
++                # than $route_ipv6_* routes to give preference to a
++                # possible default route in them.
++                if [ -n "''${ifconfig_ipv6_remote-}" ]; then
++                  ip -6 route replace default \
++                    via "$ifconfig_ipv6_remote" metric 200
++                fi
++                ${script}
++              ''}
++            '';
++        down =
++          script:
 +          let
 +            init = ''
 +              export PATH=${PATH name}
 +          let
 +            init = ''
 +              export PATH=${PATH name}
-+              ${optionalString cfg.updateResolvConf
-+                 "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
++              ${optionalString cfg.updateResolvConf "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
 +            '';
 +          in
 +            '';
 +          in
-+          if cfg.netns == null
-+          then ''
-+            ${init}
-+            ${script}
-+          ''
-+          else ''
-+            export PATH=${PATH name}
-+            ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-down-netns.sh" ''
++          if cfg.netns == null then
++            ''
 +              ${init}
 +              ${script}
 +              ${init}
 +              ${script}
-+            ''}
-+            rm -f /etc/netns/'${cfg.netns}'/resolv.conf
-+          '';
++            ''
++          else
++            ''
++              export PATH=${PATH name}
++              ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-down-netns.sh" ''
++                ${init}
++                ${script}
++              ''}
++              rm -f /etc/netns/'${cfg.netns}'/resolv.conf
++            '';
 +      };
  
      in
      {
 +      };
  
      in
      {
-@@ -61,11 +211,14 @@ let
+@@ -67,6 +221,8 @@ let
  
        wantedBy = optional cfg.autoStart "multi-user.target";
        after = [ "network.target" ];
 +      bindsTo = optional (cfg.netns != null) "netns-${cfg.netns}.service";
 +      requires = optional (cfg.netns != null) "netns-${cfg.netns}.service";
  
  
        wantedBy = optional cfg.autoStart "multi-user.target";
        after = [ "network.target" ];
 +      bindsTo = optional (cfg.netns != null) "netns-${cfg.netns}.service";
 +      requires = optional (cfg.netns != null) "netns-${cfg.netns}.service";
  
-       path = [ pkgs.iptables pkgs.iproute2 pkgs.nettools ];
+       path = [
+         pkgs.iptables
+@@ -76,6 +232,7 @@ let
  
        serviceConfig.ExecStart = "@${openvpn}/sbin/openvpn openvpn --suppress-timestamps --config ${configFile}";
        serviceConfig.Restart = "always";
  
        serviceConfig.ExecStart = "@${openvpn}/sbin/openvpn openvpn --suppress-timestamps --config ${configFile}";
        serviceConfig.Restart = "always";
@@ -270,7 +277,7 @@ index 56b1f6f5ab8f..9805099789b1 100644
        serviceConfig.Type = "notify";
      };
  
        serviceConfig.Type = "notify";
      };
  
-@@ -97,30 +250,30 @@ in
+@@ -109,30 +266,30 @@ in
        example = literalExpression ''
          {
            server = {
        example = literalExpression ''
          {
            server = {
@@ -320,174 +327,271 @@ index 56b1f6f5ab8f..9805099789b1 100644
            };
          }
        '';
            };
          }
        '';
-@@ -134,36 +287,80 @@ in
-         attribute name.
-       '';
+@@ -148,82 +305,180 @@ in
  
  
--      type = with types; attrsOf (submodule {
-+      type = with types; attrsOf (submodule ({ name, config, options, ... }: {
+       type =
+         with types;
+-        attrsOf (submodule {
++        attrsOf (
++          submodule (
++            {
++              name,
++              config,
++              options,
++              ...
++            }:
++            {
  
  
--        options = {
-+        options = rec {
-+          enable = mkEnableOption "OpenVPN server" // { default = true; };
+-          options = {
++              options = rec {
++                enable = mkEnableOption "OpenVPN server" // {
++                  default = true;
++                };
  
  
--          config = mkOption {
--            type = types.lines;
-+          settings = mkOption {
-             description = ''
-               Configuration of this OpenVPN instance.  See
-               {manpage}`openvpn(8)`
-               for details.
+-            config = mkOption {
+-              type = types.lines;
+-              description = ''
+-                Configuration of this OpenVPN instance.  See
+-                {manpage}`openvpn(8)`
+-                for details.
++                settings = mkOption {
++                  description = ''
++                    Configuration of this OpenVPN instance.  See
++                    {manpage}`openvpn(8)`
++                    for details.
  
  
-               To import an external config file, use the following definition:
--              `config = "config /path/to/config.ovpn"`
--            '';
--          };
+               To import an external config file, use the following definition:
+-                `config = "config /path/to/config.ovpn"`
+-              '';
+-            };
 -
 -
--          up = mkOption {
--            default = "";
--            type = types.lines;
--            description = ''
--              Shell commands executed when the instance is starting.
--            '';
--          };
+-            up = mkOption {
+-              default = "";
+-              type = types.lines;
+-              description = ''
+-                Shell commands executed when the instance is starting.
+-              '';
+-            };
 -
 -
--          down = mkOption {
--            default = "";
--            type = types.lines;
--            description = ''
--              Shell commands executed when the instance is shutting down.
-+              config = /path/to/config.ovpn;
-             '';
-+            default = { };
-+            type = types.submodule {
-+              freeformType = with types;
-+                attrsOf (
-+                  nullOr (
-+                    oneOf [
-+                      bool
-+                      int
-+                      str
-+                      path
-+                      (listOf (oneOf [ bool int str path ]))
-+                    ]
-+                  )
-+                );
-+              options.dev = mkOption {
-+                default = null;
-+                type = types.str;
-+                description = ''
-+                  Shell commands executed when the instance is starting.
-+                '';
-+              };
-+              options.down = mkOption {
-+                default = "";
-+                type = types.lines;
-+                description = ''
-+                  Shell commands executed when the instance is shutting down.
-+                '';
-+              };
-+              options.errors-to-stderr = mkOption {
-+                default = true;
-+                type = types.bool;
-+                description = ''
-+                  Output errors to stderr instead of stdout
-+                  unless log output is redirected by one of the `--log` options.
-+                '';
-+              };
-+              options.route-up = mkOption {
-+                default = "";
-+                type = types.lines;
-+                description = ''
-+                  Run command after routes are added.
-+                '';
-+              };
-+              options.up = mkOption {
-+                default = "";
-+                type = types.lines;
-+                description = ''
-+                  Shell commands executed when the instance is starting.
-+                '';
-+              };
-+              options.script-security = mkOption {
-+                default = 1;
-+                type = types.enum [ 1 2 3 ];
-+                description = ''
-+                  - 1 — (Default) Only call built-in executables such as ifconfig, ip, route, or netsh.
-+                  - 2 — Allow calling of built-in executables and user-defined scripts.
-+                  - 3 — Allow passwords to be passed to scripts via environmental variables (potentially unsafe).
-+                '';
-+              };
-+            };
-           };
-           autoStart = mkOption {
-@@ -172,6 +369,10 @@ in
-             description = "Whether this OpenVPN instance should be started automatically.";
-           };
+-            down = mkOption {
+-              default = "";
+-              type = types.lines;
+-              description = ''
+-                Shell commands executed when the instance is shutting down.
+-              '';
+-            };
+-
+-            autoStart = mkOption {
+-              default = true;
+-              type = types.bool;
+-              description = "Whether this OpenVPN instance should be started automatically.";
+-            };
+-
+-            updateResolvConf = mkOption {
+-              default = false;
+-              type = types.bool;
+-              description = ''
+-                Use the script from the update-resolv-conf package to automatically
+-                update resolv.conf with the DNS information provided by openvpn. The
+-                script will be run after the "up" commands and before the "down" commands.
+-              '';
+-            };
+-
+-            authUserPass = mkOption {
+-              default = null;
+-              description = ''
+-                This option can be used to store the username / password credentials
+-                with the "auth-user-pass" authentication method.
+-
+-                WARNING: Using this option will put the credentials WORLD-READABLE in the Nix store!
+-              '';
+-              type = types.nullOr (
+-                types.submodule {
+-
+-                  options = {
+-                    username = mkOption {
+-                      description = "The username to store inside the credentials file.";
++                    To import an external config file, use the following definition:
++                    config = /path/to/config.ovpn;
++                  '';
++                  default = { };
++                  type = types.submodule {
++                    freeformType =
++                      with types;
++                      attrsOf (
++                        nullOr (oneOf [
++                          bool
++                          int
++                          str
++                          path
++                          (listOf (oneOf [
++                            bool
++                            int
++                            str
++                            path
++                          ]))
++                        ])
++                      );
++                    options.dev = mkOption {
++                      default = null;
+                       type = types.str;
++                      description = ''
++                        Shell commands executed when the instance is starting.
++                      '';
+                     };
+-
+-                    password = mkOption {
+-                      description = "The password to store inside the credentials file.";
+-                      type = types.str;
++                    options.down = mkOption {
++                      default = "";
++                      type = types.lines;
++                      description = ''
++                        Shell commands executed when the instance is shutting down.
++                      '';
++                    };
++                    options.errors-to-stderr = mkOption {
++                      default = true;
++                      type = types.bool;
++                      description = ''
++                        Output errors to stderr instead of stdout
++                        unless log output is redirected by one of the `--log` options.
++                      '';
++                    };
++                    options.route-up = mkOption {
++                      default = "";
++                      type = types.lines;
++                      description = ''
++                        Run command after routes are added.
++                      '';
++                    };
++                    options.up = mkOption {
++                      default = "";
++                      type = types.lines;
++                      description = ''
++                        Shell commands executed when the instance is starting.
++                      '';
++                    };
++                    options.script-security = mkOption {
++                      default = 1;
++                      type = types.enum [
++                        1
++                        2
++                        3
++                      ];
++                      description = ''
++                        - 1 — (Default) Only call built-in executables such as ifconfig, ip, route, or netsh.
++                        - 2 — Allow calling of built-in executables and user-defined scripts.
++                        - 3 — Allow passwords to be passed to scripts via environmental variables (potentially unsafe).
++                      '';
+                     };
+                   };
+-                }
+-              );
+-            };
+-          };
++                };
  
  
-+          # Legacy options
-+          down = (elemAt settings.type.functor.payload.modules 0).options.down;
-+          up = (elemAt settings.type.functor.payload.modules 0).options.up;
+-        });
++                autoStart = mkOption {
++                  default = true;
++                  type = types.bool;
++                  description = "Whether this OpenVPN instance should be started automatically.";
++                };
 +
 +
-           updateResolvConf = mkOption {
-             default = false;
-             type = types.bool;
-@@ -205,9 +406,41 @@ in
-               };
-             });
-           };
++                # Legacy options
++                down = (elemAt settings.type.functor.payload.modules 0).options.down;
++                up = (elemAt settings.type.functor.payload.modules 0).options.up;
 +
 +
-+          netns = mkOption {
-+            default = null;
-+            type = with types; nullOr str;
-+            description = "Network namespace.";
-+          };
-         };
--      });
-+        config.settings = mkMerge
-+          [
-+            (mkIf (config.netns != null) {
-+              # Useless to setup the interface
-+              # because moving it to the netns will reset it
-+              ifconfig-noexec = true;
-+              route-noexec = true;
-+              script-security = 2;
-+            })
-+            (mkIf (config.authUserPass != null) {
-+              auth-user-pass = pkgs.writeText "openvpn-auth-user-pass-${name}" ''
-+                ${config.authUserPass.username}
-+                ${config.authUserPass.password}
-+              '';
-+            })
-+            (mkIf config.updateResolvConf {
-+              script-security = 2;
-+            })
-+            {
-+              # Aliases legacy options
-+              down = modules.mkAliasAndWrapDefsWithPriority id (options.down or { });
-+              up = modules.mkAliasAndWrapDefsWithPriority id (options.up or { });
-+            }
-+          ];
++                updateResolvConf = mkOption {
++                  default = false;
++                  type = types.bool;
++                  description = ''
++                    Use the script from the update-resolv-conf package to automatically
++                    update resolv.conf with the DNS information provided by openvpn. The
++                    script will be run after the "up" commands and before the "down" commands.
++                  '';
++                };
 +
 +
++                authUserPass = mkOption {
++                  default = null;
++                  description = ''
++                    This option can be used to store the username / password credentials
++                    with the "auth-user-pass" authentication method.
 +
 +
-+      }));
++                    WARNING: Using this option will put the credentials WORLD-READABLE in the Nix store!
++                  '';
++                  type = types.nullOr (
++                    types.submodule {
++
++                      options = {
++                        username = mkOption {
++                          description = "The username to store inside the credentials file.";
++                          type = types.str;
++                        };
++
++                        password = mkOption {
++                          description = "The password to store inside the credentials file.";
++                          type = types.str;
++                        };
++                      };
++                    }
++                  );
++                };
++
++                netns = mkOption {
++                  default = null;
++                  type = with types; nullOr str;
++                  description = "Network namespace.";
++                };
++              };
++
++              config.settings = mkMerge [
++                (mkIf (config.netns != null) {
++                  # Useless to setup the interface
++                  # because moving it to the netns will reset it
++                  ifconfig-noexec = true;
++                  route-noexec = true;
++                  script-security = 2;
++                })
++                (mkIf (config.authUserPass != null) {
++                  auth-user-pass = pkgs.writeText "openvpn-auth-user-pass-${name}" ''
++                    ${config.authUserPass.username}
++                    ${config.authUserPass.password}
++                  '';
++                })
++                (mkIf config.updateResolvConf {
++                  script-security = 2;
++                })
++                {
++                  # Aliases legacy options
++                  down = modules.mkAliasAndWrapDefsWithPriority id (options.down or { });
++                  up = modules.mkAliasAndWrapDefsWithPriority id (options.up or { });
++                }
++              ];
++
++            }
++          )
++        );
  
      };
  
  
      };
  
-@@ -222,9 +455,9 @@ in
+@@ -237,13 +492,13 @@ in
  
    ###### implementation
  
 -  config = mkIf (cfg.servers != { }) {
 +  config = mkIf (enabledServers != { }) {
  
  
    ###### implementation
  
 -  config = mkIf (cfg.servers != { }) {
 +  config = mkIf (enabledServers != { }) {
  
--    systemd.services = (listToAttrs (mapAttrsToList (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers))
-+    systemd.services = (listToAttrs (mapAttrsToList (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) enabledServers))
+     systemd.services =
+       (listToAttrs (
+         mapAttrsToList (
+           name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)
+-        ) cfg.servers
++        ) enabledServers
+       ))
        // restartService;
  
        // restartService;
  
-     environment.systemPackages = [ openvpn ];
 -- 
 -- 
-2.44.1
+2.47.2
 
 
diff --git a/nixpkgs/patches/syncoid/0001-nixos-sanoid-add-utilities-useful-to-syncoid.patch b/nixpkgs/patches/syncoid/0001-nixos-sanoid-add-utilities-useful-to-syncoid.patch
new file mode 100644 (file)
index 0000000..943dc55
--- /dev/null
@@ -0,0 +1,30 @@
+From 737f9dc0347e4c37316d219dee482dc298d9a6fb Mon Sep 17 00:00:00 2001
+From: Julien Moutinho <julm+nixpkgs@sourcephile.fr>
+Date: Sat, 27 Nov 2021 02:42:20 +0100
+Subject: [PATCH 1/2] nixos/sanoid: add utilities useful to syncoid
+
+---
+ nixos/modules/services/backup/sanoid.nix | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/nixos/modules/services/backup/sanoid.nix b/nixos/modules/services/backup/sanoid.nix
+index 1baf08f462..cd3c1d4594 100644
+--- a/nixos/modules/services/backup/sanoid.nix
++++ b/nixos/modules/services/backup/sanoid.nix
+@@ -254,6 +254,13 @@ in
+       after = [ "zfs.target" ];
+       startAt = cfg.interval;
+     };
++
++    # Put those packages in the global environment
++    # so that syncoid can find them when targeting this host through ssh.
++    environment.systemPackages = with pkgs; [
++      lzop
++      mbuffer
++    ];
+   };
+   meta.maintainers = with lib.maintainers; [ lopsided98 ];
+-- 
+2.47.2
+
diff --git a/nixpkgs/patches/syncoid/0002-nixos-syncoid-use-DynamicUser.patch b/nixpkgs/patches/syncoid/0002-nixos-syncoid-use-DynamicUser.patch
new file mode 100644 (file)
index 0000000..b0df633
--- /dev/null
@@ -0,0 +1,717 @@
+From 0e837ecd5ffb5e8366a5e2878838ba691b31cd99 Mon Sep 17 00:00:00 2001
+From: Julien Moutinho <julm+nixpkgs@sourcephile.fr>
+Date: Sat, 27 Nov 2021 02:50:39 +0100
+Subject: [PATCH 2/2] nixos/syncoid: use DynamicUser=
+
+HistoryNote: the following changes
+were previously in separate commits
+but have been squashed and nixfmt-rfc-style
+on be able to rebase them
+onto a treewide change applying nixfmt-rfc-style
+on nixos/modules/services/backup/syncoid.nix
+which introduced multiple untrackable conflicts.
+
+Changes:
+
+- nixos/syncoid: add destroy to localTargetAllow's default
+
+  syncoid[3282027]: Resuming interrupted zfs send/receive from rpool/var/mail to losurdo/backup/mermet/var/mail (~ UNKNOWN remaining):
+  syncoid[3282885]: cannot resume send: 'rpool/var/mail@autosnap_2021-10-17_15:00:19_hourly' used in the initial send no longer exists
+  syncoid[3282897]: cannot receive: failed to read from stream
+  syncoid[3282027]: WARN: resetting partially receive state because the snapshot source no longer exists
+  syncoid[3283326]: cannot destroy 'losurdo/backup/mermet/var/mail/%recv': permission denied
+
+- nixos/syncoid: allow @timer syscalls
+
+- nixos/syncoid: set TZ envvar
+
+- nixos/syncoid: test sending to multiple targets
+
+- nixos/syncoid: don't delegate permissions to source's parent
+
+  See https://github.com/NixOS/nixpkgs/pull/165204
+  and https://github.com/NixOS/nixpkgs/pull/180111
+
+- nixos/syncoid: add support for LoadCredentialEncrypted=
+
+- nixos/syncoid: zfs-unallow unused dynamic users
+
+- nixos/syncoid: use NFTSet=
+
+Changes between the treewide nixfmt-rfc-style
+and those changes have been preserved:
+
+- 71967c47e54c56557c0ee7bec7fe554c018e37c4 nixos/syncoid: allow interval to be list of strings
+- f34483be5ee2418a563545a56743b7b59c549935 nixosTests: handleTest -> runTest, batch 1
+---
+ nixos/modules/services/backup/syncoid.nix | 454 +++++++++++-----------
+ nixos/tests/sanoid.nix                    |  78 ++--
+ 2 files changed, 269 insertions(+), 263 deletions(-)
+
+diff --git a/nixos/modules/services/backup/syncoid.nix b/nixos/modules/services/backup/syncoid.nix
+index 8b4c59155f..7d344a1ad0 100644
+--- a/nixos/modules/services/backup/syncoid.nix
++++ b/nixos/modules/services/backup/syncoid.nix
+@@ -7,7 +7,7 @@
+ let
+   cfg = config.services.syncoid;
+-  # Extract local dasaset names (so no datasets containing "@")
++  # Extract local dataset names (so no datasets containing "@")
+   localDatasetName =
+     d:
+     lib.optionals (d != null) (
+@@ -20,81 +20,9 @@ let
+   # Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
+   escapeUnitName =
+     name:
+-    lib.concatMapStrings (s: if lib.isList s then "-" else s) (
++    lib.concatMapStrings (s: if builtins.isList s then "-" else s) (
+       builtins.split "[^a-zA-Z0-9_.\\-]+" name
+     );
+-
+-  # Function to build "zfs allow" commands for the filesystems we've delegated
+-  # permissions to. It also checks if the target dataset exists before
+-  # delegating permissions, if it doesn't exist we delegate it to the parent
+-  # dataset (if it exists). This should solve the case of provisoning new
+-  # datasets.
+-  buildAllowCommand =
+-    permissions: dataset:
+-    (
+-      "-+${pkgs.writeShellScript "zfs-allow-${dataset}" ''
+-        # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
+-
+-        # Run a ZFS list on the dataset to check if it exists
+-        if ${
+-          lib.escapeShellArgs [
+-            "/run/booted-system/sw/bin/zfs"
+-            "list"
+-            dataset
+-          ]
+-        } 2> /dev/null; then
+-          ${lib.escapeShellArgs [
+-            "/run/booted-system/sw/bin/zfs"
+-            "allow"
+-            cfg.user
+-            (lib.concatStringsSep "," permissions)
+-            dataset
+-          ]}
+-        ${lib.optionalString ((builtins.dirOf dataset) != ".") ''
+-          else
+-            ${lib.escapeShellArgs [
+-              "/run/booted-system/sw/bin/zfs"
+-              "allow"
+-              cfg.user
+-              (lib.concatStringsSep "," permissions)
+-              # Remove the last part of the path
+-              (builtins.dirOf dataset)
+-            ]}
+-        ''}
+-        fi
+-      ''}"
+-    );
+-
+-  # Function to build "zfs unallow" commands for the filesystems we've
+-  # delegated permissions to. Here we unallow both the target but also
+-  # on the parent dataset because at this stage we have no way of
+-  # knowing if the allow command did execute on the parent dataset or
+-  # not in the pre-hook. We can't run the same if in the post hook
+-  # since the dataset should have been created at this point.
+-  buildUnallowCommand =
+-    permissions: dataset:
+-    (
+-      "-+${pkgs.writeShellScript "zfs-unallow-${dataset}" ''
+-        # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
+-        ${lib.escapeShellArgs [
+-          "/run/booted-system/sw/bin/zfs"
+-          "unallow"
+-          cfg.user
+-          (lib.concatStringsSep "," permissions)
+-          dataset
+-        ]}
+-        ${lib.optionalString ((builtins.dirOf dataset) != ".") (
+-          lib.escapeShellArgs [
+-            "/run/booted-system/sw/bin/zfs"
+-            "unallow"
+-            cfg.user
+-            (lib.concatStringsSep "," permissions)
+-            # Remove the last part of the path
+-            (builtins.dirOf dataset)
+-          ]
+-        )}
+-      ''}"
+-    );
+ in
+ {
+@@ -120,38 +48,23 @@ in
+       '';
+     };
+-    user = lib.mkOption {
+-      type = lib.types.str;
+-      default = "syncoid";
+-      example = "backup";
+-      description = ''
+-        The user for the service. ZFS privilege delegation will be
+-        automatically configured for any local pools used by syncoid if this
+-        option is set to a user other than root. The user will be given the
+-        "hold" and "send" privileges on any pool that has datasets being sent
+-        and the "create", "mount", "receive", and "rollback" privileges on
+-        any pool that has datasets being received.
+-      '';
+-    };
+-
+-    group = lib.mkOption {
+-      type = lib.types.str;
+-      default = "syncoid";
+-      example = "backup";
+-      description = "The group for the service.";
+-    };
+-
+     sshKey = lib.mkOption {
+       type = with lib.types; nullOr (coercedTo path toString str);
+       default = null;
+       description = ''
+-        SSH private key file to use to login to the remote system. Can be
+-        overridden in individual commands.
++        SSH private key file to use to login to the remote system.
++        It can be overridden in individual commands.
++        It is loaded using `LoadCredentialEncrypted=`
++        when its path is prefixed by a credential name and colon,
++        otherwise `LoadCredential=` is used.
++        For more SSH tuning, you may use syncoid's `--sshoption`
++        in {option}`services.syncoid.commonArgs`
++        and/or in the `extraArgs` of a specific command.
+       '';
+     };
+     localSourceAllow = lib.mkOption {
+-      type = lib.types.listOf lib.types.str;
++      type = with lib.types; listOf str;
+       # Permissions snapshot and destroy are in case --no-sync-snap is not used
+       default = [
+         "bookmark"
+@@ -162,19 +75,22 @@ in
+         "mount"
+       ];
+       description = ''
+-        Permissions granted for the {option}`services.syncoid.user` user
+-        for local source datasets. See
+-        <https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
++        Permissions granted for the syncoid user for local source datasets.
++        See <https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
+         for available permissions.
+       '';
+     };
+     localTargetAllow = lib.mkOption {
+-      type = lib.types.listOf lib.types.str;
++      type = with lib.types; listOf str;
++      # Permission destroy is required to reset broken receive states (zfs receive -A),
++      # which syncoid does when it fails to resume a receive state,
++      # when the snapshot it refers to has been destroyed on the source.
+       default = [
+         "change-key"
+         "compression"
+         "create"
++        "destroy"
+         "mount"
+         "mountpoint"
+         "receive"
+@@ -187,9 +103,8 @@ in
+         "rollback"
+       ];
+       description = ''
+-        Permissions granted for the {option}`services.syncoid.user` user
+-        for local target datasets. See
+-        <https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
++        Permissions granted for the syncoid user for local target datasets.
++        See <https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
+         for available permissions.
+         Make sure to include the `change-key` permission if you send raw encrypted datasets,
+         the `compression` permission if you send raw compressed datasets, and so on.
+@@ -198,7 +113,7 @@ in
+     };
+     commonArgs = lib.mkOption {
+-      type = lib.types.listOf lib.types.str;
++      type = with lib.types; listOf str;
+       default = [ ];
+       example = [ "--no-sync-snap" ];
+       description = ''
+@@ -296,13 +211,13 @@ in
+                 '';
+               };
+-              useCommonArgs = lib.mkOption {
+-                type = lib.types.bool;
+-                default = true;
+-                description = ''
+-                  Whether to add the configured common arguments to this command.
+-                '';
+-              };
++              useCommonArgs =
++                lib.mkEnableOption ''
++                  configured common arguments to this command
++                ''
++                // {
++                  default = true;
++                };
+               service = lib.mkOption {
+                 type = lib.types.attrs;
+@@ -341,139 +256,216 @@ in
+   # Implementation
+   config = lib.mkIf cfg.enable {
+-    users = {
+-      users = lib.mkIf (cfg.user == "syncoid") {
+-        syncoid = {
+-          group = cfg.group;
+-          isSystemUser = true;
+-          # For syncoid to be able to create /var/lib/syncoid/.ssh/
+-          # and to use custom ssh_config or known_hosts.
+-          home = "/var/lib/syncoid";
+-          createHome = false;
+-        };
+-      };
+-      groups = lib.mkIf (cfg.group == "syncoid") {
+-        syncoid = { };
+-      };
+-    };
+-
+     systemd.services = lib.mapAttrs' (
+       name: c:
++      let
++        sshKeyCred = builtins.split ":" c.sshKey;
++      in
+       lib.nameValuePair "syncoid-${escapeUnitName name}" (
+         lib.mkMerge [
+           {
+             description = "Syncoid ZFS synchronization from ${c.source} to ${c.target}";
+             after = [ "zfs.target" ];
+             startAt = cfg.interval;
+-            # syncoid may need zpool to get feature@extensible_dataset
+-            path = [ "/run/booted-system/sw/bin/" ];
+-            serviceConfig = {
+-              ExecStartPre =
+-                (map (buildAllowCommand c.localSourceAllow) (localDatasetName c.source))
+-                ++ (map (buildAllowCommand c.localTargetAllow) (localDatasetName c.target));
+-              ExecStopPost =
+-                (map (buildUnallowCommand c.localSourceAllow) (localDatasetName c.source))
+-                ++ (map (buildUnallowCommand c.localTargetAllow) (localDatasetName c.target));
+-              ExecStart = lib.escapeShellArgs (
+-                [ "${cfg.package}/bin/syncoid" ]
+-                ++ lib.optionals c.useCommonArgs cfg.commonArgs
+-                ++ lib.optional c.recursive "-r"
+-                ++ lib.optionals (c.sshKey != null) [
+-                  "--sshkey"
+-                  c.sshKey
+-                ]
+-                ++ c.extraArgs
+-                ++ [
+-                  "--sendoptions"
+-                  c.sendOptions
+-                  "--recvoptions"
+-                  c.recvOptions
+-                  "--no-privilege-elevation"
+-                  c.source
+-                  c.target
+-                ]
+-              );
+-              User = cfg.user;
+-              Group = cfg.group;
+-              StateDirectory = [ "syncoid" ];
+-              StateDirectoryMode = "700";
+-              # Prevent SSH control sockets of different syncoid services from interfering
+-              PrivateTmp = true;
+-              # Permissive access to /proc because syncoid
+-              # calls ps(1) to detect ongoing `zfs receive`.
+-              ProcSubset = "all";
+-              ProtectProc = "default";
++            # Here we explicitly use the booted system to guarantee the stable API needed by ZFS.
++            # Moreover syncoid may need zpool to get feature@extensible_dataset.
++            path = [ "/run/booted-system/sw" ];
++            # Prevents missing snapshots during DST changes
++            environment.TZ = "UTC";
++            # A custom LD_LIBRARY_PATH is needed to access in `getent passwd`
++            # the systemd's entry about the DynamicUser=,
++            # so that ssh won't fail with: "No user exists for uid $UID".
++            environment.LD_LIBRARY_PATH = config.system.nssModules.path;
++            serviceConfig =
++              {
++                ExecStartPre =
++                  # Recursively remove any residual permissions
++                  # given on local+descendant datasets (source, target or target's parent)
++                  # to any currently unknown (hence unused) systemd dynamic users (UID/GID range 61184…65519),
++                  # which happens when a crash has occurred
++                  # during any previous run of a syncoid-*.service (not only this one).
++                  map
++                    (
++                      dataset:
++                      "+"
++                      + pkgs.writeShellScript "zfs-unallow-unused-dynamic-users" ''
++                        set -eu
++                        zfs allow "$1" |
++                        sed -ne 's/^\t\(user\|group\) (unknown: \([0-9]\+\)).*/\1 \2/p' |
++                        {
++                          declare -a uids
++                          while read -r role id; do
++                            if [ "$id" -ge 61184 ] && [ "$id" -le 65519 ]; then
++                              case "$role" in
++                                (user) uids+=("$id");;
++                              esac
++                            fi
++                          done
++                          zfs unallow -r -u "$(printf %s, "''${uids[@]}")" "$1"
++                        }
++                      ''
++                      + " "
++                      + lib.escapeShellArg dataset
++                    )
++                    (
++                      localDatasetName c.source
++                      ++ localDatasetName c.target
++                      ++ map builtins.dirOf (localDatasetName c.target)
++                    )
++                  ++
++                    # For a local source, allow the localSourceAllow ZFS permissions.
++                    map (
++                      dataset:
++                      "+/run/booted-system/sw/bin/zfs allow $USER "
++                      + lib.escapeShellArgs [
++                        (lib.concatStringsSep "," c.localSourceAllow)
++                        dataset
++                      ]
++                    ) (localDatasetName c.source)
++                  ++
++                    # For a local target, check if the dataset exists before delegating permissions,
++                    # and if it doesn't exist, delegate it to the parent dataset.
++                    # This should solve the case of provisioning new datasets.
++                    map (
++                      dataset:
++                      "+"
++                      + pkgs.writeShellScript "zfs-allow-target" ''
++                        dataset="$1"
++                        # Run a ZFS list on the dataset to check if it exists
++                        zfs list "$dataset" >/dev/null 2>/dev/null ||
++                          dataset="$(dirname "$dataset")"
++                        zfs allow "$USER" ${lib.escapeShellArg (lib.concatStringsSep "," c.localTargetAllow)} "$dataset"
++                      ''
++                      + " "
++                      + lib.escapeShellArg dataset
++                    ) (localDatasetName c.target);
++                ExecStopPost =
++                  let
++                    zfsUnallow = dataset: "+/run/booted-system/sw/bin/zfs unallow $USER " + lib.escapeShellArg dataset;
++                  in
++                  map zfsUnallow (localDatasetName c.source)
++                  ++
++                    # For a local target, unallow both the dataset and its parent,
++                    # because at this stage we have no way of knowing if the allow command
++                    # did execute on the parent dataset or not in the ExecStartPre=.
++                    # We can't run the same if-then-else in the post hook
++                    # since the dataset should have been created at this point.
++                    lib.concatMap (dataset: [
++                      (zfsUnallow dataset)
++                      (zfsUnallow (builtins.dirOf dataset))
++                    ]) (localDatasetName c.target);
++                ExecStart = lib.escapeShellArgs (
++                  [ "${cfg.package}/bin/syncoid" ]
++                  ++ lib.optionals c.useCommonArgs cfg.commonArgs
++                  ++ lib.optional c.recursive "--recursive"
++                  ++ lib.optionals (c.sshKey != null) [
++                    "--sshkey"
++                    "\${CREDENTIALS_DIRECTORY}/${if lib.length sshKeyCred > 1 then lib.head sshKeyCred else "sshKey"}"
++                  ]
++                  ++ c.extraArgs
++                  ++ [
++                    "--sendoptions"
++                    c.sendOptions
++                    "--recvoptions"
++                    c.recvOptions
++                    "--no-privilege-elevation"
++                    c.source
++                    c.target
++                  ]
++                );
++                DynamicUser = true;
++                NFTSet = lib.optional config.networking.nftables.enable "user:inet:filter:nixos_syncoid_uids";
++                # Prevent SSH control sockets of different syncoid services from interfering
++                PrivateTmp = true;
++                # Permissive access to /proc because syncoid
++                # calls ps(1) to detect ongoing `zfs receive`.
++                ProcSubset = "all";
++                ProtectProc = "default";
+-              # The following options are only for optimizing:
+-              # systemd-analyze security | grep syncoid-'*'
+-              AmbientCapabilities = "";
+-              CapabilityBoundingSet = "";
+-              DeviceAllow = [ "/dev/zfs" ];
+-              LockPersonality = true;
+-              MemoryDenyWriteExecute = true;
+-              NoNewPrivileges = true;
+-              PrivateDevices = true;
+-              PrivateMounts = true;
+-              PrivateNetwork = lib.mkDefault false;
+-              PrivateUsers = false; # Enabling this breaks on zfs-2.2.0
+-              ProtectClock = true;
+-              ProtectControlGroups = true;
+-              ProtectHome = true;
+-              ProtectHostname = true;
+-              ProtectKernelLogs = true;
+-              ProtectKernelModules = true;
+-              ProtectKernelTunables = true;
+-              ProtectSystem = "strict";
+-              RemoveIPC = true;
+-              RestrictAddressFamilies = [
+-                "AF_UNIX"
+-                "AF_INET"
+-                "AF_INET6"
+-              ];
+-              RestrictNamespaces = true;
+-              RestrictRealtime = true;
+-              RestrictSUIDSGID = true;
+-              RootDirectory = "/run/syncoid/${escapeUnitName name}";
+-              RootDirectoryStartOnly = true;
+-              BindPaths = [ "/dev/zfs" ];
+-              BindReadOnlyPaths = [
+-                builtins.storeDir
+-                "/etc"
+-                "/run"
+-                "/bin/sh"
+-              ];
+-              # Avoid useless mounting of RootDirectory= in the own RootDirectory= of ExecStart='s mount namespace.
+-              InaccessiblePaths = [ "-+/run/syncoid/${escapeUnitName name}" ];
+-              MountAPIVFS = true;
+-              # Create RootDirectory= in the host's mount namespace.
+-              RuntimeDirectory = [ "syncoid/${escapeUnitName name}" ];
+-              RuntimeDirectoryMode = "700";
+-              SystemCallFilter = [
+-                "@system-service"
+-                # Groups in @system-service which do not contain a syscall listed by:
+-                # perf stat -x, 2>perf.log -e 'syscalls:sys_enter_*' syncoid …
+-                # awk >perf.syscalls -F "," '$1 > 0 {sub("syscalls:sys_enter_","",$3); print $3}' perf.log
+-                # systemd-analyze syscall-filter | grep -v -e '#' | sed -e ':loop; /^[^ ]/N; s/\n //; t loop' | grep $(printf ' -e \\<%s\\>' $(cat perf.syscalls)) | cut -f 1 -d ' '
+-                "~@aio"
+-                "~@chown"
+-                "~@keyring"
+-                "~@memlock"
+-                "~@privileged"
+-                "~@resources"
+-                "~@setuid"
+-                "~@timer"
+-              ];
+-              SystemCallArchitectures = "native";
+-              # This is for BindPaths= and BindReadOnlyPaths=
+-              # to allow traversal of directories they create in RootDirectory=.
+-              UMask = "0066";
+-            };
++                # The following options are only for optimizing:
++                # systemd-analyze security | grep syncoid-'*'
++                AmbientCapabilities = "";
++                CapabilityBoundingSet = "";
++                DeviceAllow = [ "/dev/zfs" ];
++                LockPersonality = true;
++                MemoryDenyWriteExecute = true;
++                NoNewPrivileges = true;
++                PrivateDevices = true;
++                PrivateMounts = true;
++                PrivateNetwork = lib.mkDefault false;
++                PrivateUsers = false; # Enabling this breaks on zfs-2.2.0
++                ProtectClock = true;
++                ProtectControlGroups = true;
++                ProtectHome = true;
++                ProtectHostname = true;
++                ProtectKernelLogs = true;
++                ProtectKernelModules = true;
++                ProtectKernelTunables = true;
++                ProtectSystem = "strict";
++                RemoveIPC = true;
++                RestrictAddressFamilies = [
++                  "AF_UNIX"
++                  "AF_INET"
++                  "AF_INET6"
++                ];
++                RestrictNamespaces = true;
++                RestrictRealtime = true;
++                RestrictSUIDSGID = true;
++                RootDirectory = "/run/syncoid/${escapeUnitName name}";
++                BindPaths = [ "/dev/zfs" ];
++                BindReadOnlyPaths = [
++                  builtins.storeDir
++                  "/etc"
++                  "/run"
++                  # Some programs hardcode /var/run
++                  # eg. /var/run/avahi-daemon/socket to resolve *.local mDNS domains
++                  "/var/run"
++                  "/bin/sh"
++                ];
++                # Avoid useless mounting of RootDirectory= in the own RootDirectory= of ExecStart='s mount namespace.
++                InaccessiblePaths = [ "-+/run/syncoid/${escapeUnitName name}" ];
++                MountAPIVFS = true;
++                # Create RootDirectory= in the host's mount namespace.
++                RuntimeDirectory = [ "syncoid/${escapeUnitName name}" ];
++                RuntimeDirectoryMode = "700";
++                SystemCallFilter = [
++                  "@system-service"
++                  # Groups in @system-service which do not contain a syscall listed by:
++                  # perf stat -x, 2>perf.log -e 'syscalls:sys_enter_*' syncoid …
++                  # awk >perf.syscalls -F "," '$1 > 0 {sub("syscalls:sys_enter_","",$3); print $3}' perf.log
++                  # systemd-analyze syscall-filter | grep -v -e '#' | sed -e ':loop; /^[^ ]/N; s/\n //; t loop' | grep $(printf ' -e \\<%s\\>' $(cat perf.syscalls)) | cut -f 1 -d ' '
++                  "~@aio"
++                  "~@chown"
++                  "~@keyring"
++                  "~@memlock"
++                  "~@privileged"
++                  "~@resources"
++                  "~@setuid"
++                ];
++                SystemCallArchitectures = "native";
++                # This is for BindPaths= and BindReadOnlyPaths=
++                # to allow traversal of directories they create in RootDirectory=.
++                UMask = "0066";
++              }
++              // (
++                if lib.length sshKeyCred > 1 then
++                  { LoadCredentialEncrypted = [ c.sshKey ]; }
++                else
++                  { LoadCredential = [ "sshKey:${c.sshKey}" ]; }
++              );
+           }
+           cfg.service
+           c.service
+         ]
+       )
+     ) cfg.commands;
++
++    networking.nftables.ruleset = lib.mkBefore ''
++      table inet filter {
++        # A set containing the dynamic UIDs of the syncoid services currently active
++        set nixos_syncoid_uids { typeof meta skuid; }
++      }
++    '';
+   };
+   meta.maintainers = with lib.maintainers; [
+diff --git a/nixos/tests/sanoid.nix b/nixos/tests/sanoid.nix
+index e42fd54dfd..eabe88b6be 100644
+--- a/nixos/tests/sanoid.nix
++++ b/nixos/tests/sanoid.nix
+@@ -50,16 +50,26 @@ in
+           enable = true;
+           sshKey = "/var/lib/syncoid/id_ecdsa";
+           commands = {
+-            # Sync snapshot taken by sanoid
+-            "pool/sanoid" = {
+-              target = "root@target:pool/sanoid";
++            # Take snapshot and sync
++            "pool/syncoid".target = "root@target:pool/syncoid";
++
++            # Sync the same dataset to different targets
++            "pool/sanoid1" = {
++              source = "pool/sanoid";
++              target = "root@target:pool/sanoid1";
++              extraArgs = [
++                "--no-sync-snap"
++                "--create-bookmark"
++              ];
++            };
++            "pool/sanoid2" = {
++              source = "pool/sanoid";
++              target = "root@target:pool/sanoid2";
+               extraArgs = [
+                 "--no-sync-snap"
+                 "--create-bookmark"
+               ];
+             };
+-            # Take snapshot and sync
+-            "pool/syncoid".target = "root@target:pool/syncoid";
+             # Test pool without parent (regression test for https://github.com/NixOS/nixpkgs/pull/180111)
+             "pool".target = "root@target:pool/full-pool";
+@@ -94,6 +104,7 @@ in
+         "zfs create pool/syncoid",
+         "udevadm settle",
+     )
++
+     target.succeed(
+         "mkdir /mnt",
+         "parted --script /dev/vdb -- mklabel msdos mkpart primary 1024M -1s",
+@@ -106,41 +117,44 @@ in
+         "mkdir -m 700 -p /var/lib/syncoid",
+         "cat '${snakeOilPrivateKey}' > /var/lib/syncoid/id_ecdsa",
+         "chmod 600 /var/lib/syncoid/id_ecdsa",
+-        "chown -R syncoid:syncoid /var/lib/syncoid/",
+     )
+-    assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set before snapshotting"
+-    assert len(source.succeed("zfs allow pool/sanoid")) == 0, "Sanoid dataset shouldn't have delegated permissions set before snapshotting"
+-    assert len(source.succeed("zfs allow pool/syncoid")) == 0, "Syncoid dataset shouldn't have delegated permissions set before snapshotting"
++    with subtest("Take snapshots with sanoid"):
++      source.succeed("touch /mnt/pool/sanoid/test.txt")
++      source.succeed("touch /mnt/pool/compat/test.txt")
++      source.systemctl("start --wait sanoid.service")
+-    # Take snapshot with sanoid
+-    source.succeed("touch /mnt/pool/sanoid/test.txt")
+-    source.succeed("touch /mnt/pool/compat/test.txt")
+-    source.systemctl("start --wait sanoid.service")
++    # Add some unused dynamic users to the stateful allow list of ZFS datasets,
++    # simulating a state where they remain after the system crashed,
++    # to check they'll be correctly removed by the syncoid services.
++    # Each syncoid service run from now may reuse at most one of them for itself.
++    source.succeed(
++        "zfs allow -u $(printf %s, {61184..61200})65519 dedup pool",
++        "zfs allow -u $(printf %s, {61184..61200})65519 dedup pool/sanoid",
++        "zfs allow -u $(printf %s, {61184..61200})65519 dedup pool/syncoid",
++    )
++
++    with subtest("sync snapshots"):
++      target.wait_for_open_port(22)
++      source.succeed("touch /mnt/pool/syncoid/test.txt")
++
++      source.systemctl("start --wait syncoid-pool-syncoid.service")
++      target.succeed("cat /mnt/pool/syncoid/test.txt")
++
++      source.systemctl("start --wait syncoid-pool-sanoid{1,2}.service")
++      target.succeed("cat /mnt/pool/sanoid1/test.txt")
++      target.succeed("cat /mnt/pool/sanoid2/test.txt")
++
++      source.systemctl("start --wait syncoid-pool.service")
++      target.succeed("[[ -d /mnt/pool/full-pool/syncoid ]]")
++
++      source.systemctl("start --wait syncoid-pool-compat.service")
++      target.succeed("cat /mnt/pool/compat/test.txt")
+     assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set after snapshotting"
+     assert len(source.succeed("zfs allow pool/sanoid")) == 0, "Sanoid dataset shouldn't have delegated permissions set after snapshotting"
+     assert len(source.succeed("zfs allow pool/syncoid")) == 0, "Syncoid dataset shouldn't have delegated permissions set after snapshotting"
+-
+-    # Sync snapshots
+-    target.wait_for_open_port(22)
+-    source.succeed("touch /mnt/pool/syncoid/test.txt")
+-    source.systemctl("start --wait syncoid-pool-sanoid.service")
+-    target.succeed("cat /mnt/pool/sanoid/test.txt")
+-    source.systemctl("start --wait syncoid-pool-syncoid.service")
+-    source.systemctl("start --wait syncoid-pool-syncoid.service")
+-    target.succeed("cat /mnt/pool/syncoid/test.txt")
+-
+     assert(len(source.succeed("zfs list -H -t snapshot pool/syncoid").splitlines()) == 1), "Syncoid should only retain one sync snapshot"
+-    source.systemctl("start --wait syncoid-pool.service")
+-    target.succeed("[[ -d /mnt/pool/full-pool/syncoid ]]")
+-
+-    source.systemctl("start --wait syncoid-pool-compat.service")
+-    target.succeed("cat /mnt/pool/compat/test.txt")
+-
+-    assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set after syncing snapshots"
+-    assert len(source.succeed("zfs allow pool/sanoid")) == 0, "Sanoid dataset shouldn't have delegated permissions set after syncing snapshots"
+-    assert len(source.succeed("zfs allow pool/syncoid")) == 0, "Syncoid dataset shouldn't have delegated permissions set after syncing snapshots"
+   '';
+ }
+-- 
+2.47.2
+
index 1840e0f4b39f535707940bc44af81cbf0fcefb35..f772c967d35011bbbcb75269ea43367f22efdb43 100644 (file)
--- a/shell.nix
+++ b/shell.nix
@@ -29,7 +29,7 @@ pkgs.mkShell {
   ];
   shellHook = ''
     echo >&2 "nix: running shellHook"
   ];
   shellHook = ''
     echo >&2 "nix: running shellHook"
-    PATH="${inputs.home-manager.defaultPackage.${system}}/bin:$PATH"
+    PATH="${inputs.home-manager.packages.${system}.default}/bin:$PATH"
     PASSWORD_STORE_DIR=$PWD
     nix-store --add-root nixpkgs.root --indirect --realise ${nixpkgsPath}
     ${shellHook}
     PASSWORD_STORE_DIR=$PWD
     nix-store --add-root nixpkgs.root --indirect --realise ${nixpkgsPath}
     ${shellHook}