From: Julien Moutinho Date: Tue, 3 Jun 2025 01:57:23 +0000 (+0200) Subject: nix: update to nixos-25.05 X-Git-Url: https://git.sourcephile.fr/julm/julm-nix.git/commitdiff_plain?ds=sidebyside nix: update to nixos-25.05 --- diff --git a/flake.lock b/flake.lock index 2301ad2..e4a2cc2 100644 --- a/flake.lock +++ b/flake.lock @@ -156,16 +156,16 @@ ] }, "locked": { - "lastModified": 1739757849, - "narHash": "sha256-Gs076ot1YuAAsYVcyidLKUMIc4ooOaRGO0PqTY7sBzA=", + "lastModified": 1748665073, + "narHash": "sha256-RMhjnPKWtCoIIHiuR9QKD7xfsKb3agxzMfJY8V9MOew=", "owner": "nix-community", "repo": "home-manager", - "rev": "9d3d080aec2a35e05a15cedd281c2384767c2cfe", + "rev": "282e1e029cb6ab4811114fc85110613d72771dea", "type": "github" }, "original": { "owner": "nix-community", - "ref": "release-24.11", + "ref": "release-25.05", "repo": "home-manager", "type": "github" } @@ -214,17 +214,17 @@ }, "nixpkgs": { "locked": { - "lastModified": 1738843498, - "narHash": "sha256-7x+Q4xgFj9UxZZO9aUDCR8h4vyYut4zPUvfj3i+jBHE=", + "lastModified": 1748263217, + "narHash": "sha256-iWS3iPlQhVn525rbB30BcAA5RZ9lrGcUMKqylAT/T14=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f5a32fa27df91dfc4b762671a0e0a859a8a0058f", + "rev": "f34483be5ee2418a563545a56743b7b59c549935", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-24.11", "repo": "nixpkgs", + "rev": "f34483be5ee2418a563545a56743b7b59c549935", "type": "github" } }, diff --git a/flake.nix b/flake.nix index b670b8a..392ae9f 100644 --- a/flake.nix +++ b/flake.nix @@ -17,9 +17,10 @@ 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"; - 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"; @@ -32,6 +33,10 @@ 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 { diff --git a/home-manager/profiles/developing.nix b/home-manager/profiles/developing.nix index b7aa011..c421b33 100644 --- a/home-manager/profiles/developing.nix +++ b/home-manager/profiles/developing.nix @@ -46,7 +46,7 @@ #pkgs.ubootTools # currently broken #pkgs.unar # currently broken pkgs.unzip - pkgs.vbetool + #pkgs.vbetool pkgs.wgetpaste pkgs.xmlstarlet pkgs.xsel diff --git a/home-manager/profiles/emacs.nix b/home-manager/profiles/emacs.nix index 7fb75bf..93e8ad9 100644 --- a/home-manager/profiles/emacs.nix +++ b/home-manager/profiles/emacs.nix @@ -42,11 +42,7 @@ 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 = { diff --git a/home-manager/profiles/essential.nix b/home-manager/profiles/essential.nix index 894abfd..47fce9d 100644 --- a/home-manager/profiles/essential.nix +++ b/home-manager/profiles/essential.nix @@ -23,7 +23,7 @@ pkgs.direnv pkgs.dislocker pkgs.dmidecode - pkgs.dstat + pkgs.dool pkgs.dust pkgs.e2fsprogs pkgs.efitools diff --git a/home-manager/profiles/gnupg.nix b/home-manager/profiles/gnupg.nix index b25e60a..c6282d3 100644 --- a/home-manager/profiles/gnupg.nix +++ b/home-manager/profiles/gnupg.nix @@ -15,7 +15,7 @@ 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 ); }; diff --git a/home-manager/profiles/lang-cmn.nix b/home-manager/profiles/lang-cmn.nix index b5e635d..a968dea 100644 --- a/home-manager/profiles/lang-cmn.nix +++ b/home-manager/profiles/lang-cmn.nix @@ -1,9 +1,11 @@ { config, pkgs, ... }: { i18n.inputMethod = { - enabled = "fcitx5"; + type = "fcitx5"; + enable = true; 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 diff --git a/home-manager/profiles/urxvt.nix b/home-manager/profiles/urxvt.nix index 7b6bdc7..d8c4a4f 100644 --- a/home-manager/profiles/urxvt.nix +++ b/home-manager/profiles/urxvt.nix @@ -2,12 +2,8 @@ { 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 = { diff --git a/home-manager/profiles/xmonad/xmonad.hs b/home-manager/profiles/xmonad/xmonad.hs index b562527..47da9eb 100644 --- a/home-manager/profiles/xmonad/xmonad.hs +++ b/home-manager/profiles/xmonad/xmonad.hs @@ -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}'\"" -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 diff --git a/homes/julm/hosts/blackberry.nix b/homes/julm/hosts/blackberry.nix index a4b1624..8e16021 100644 --- a/homes/julm/hosts/blackberry.nix +++ b/homes/julm/hosts/blackberry.nix @@ -51,7 +51,7 @@ pkgs.calibre #pkgs.zotero pkgs.evince - pkgs.marble + pkgs.kdePackages.marble pkgs.gcompris pkgs.frozen-bubble pkgs.neverball diff --git a/homes/julm/hosts/oignon.nix b/homes/julm/hosts/oignon.nix index fb1fd3b..49ebd23 100644 --- a/homes/julm/hosts/oignon.nix +++ b/homes/julm/hosts/oignon.nix @@ -58,7 +58,7 @@ pkgs.calibre pkgs.zotero pkgs.evince - pkgs.marble + pkgs.kdePackages.marble pkgs.gcompris pkgs.frozen-bubble pkgs.neverball diff --git a/homes/julm/hosts/pumpkin.nix b/homes/julm/hosts/pumpkin.nix index 2a118dc..1abd275 100644 --- a/homes/julm/hosts/pumpkin.nix +++ b/homes/julm/hosts/pumpkin.nix @@ -60,7 +60,7 @@ 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 @@ -69,13 +69,13 @@ pkgs.calibre pkgs.zotero pkgs.evince - pkgs.marble + pkgs.kdePackages.marble pkgs.gcompris pkgs.frozen-bubble pkgs.neverball pkgs.tuxpaint pkgs.rmg - pkgs.veloren + #pkgs.veloren pkgs.shipwright pkgs.steam-run pkgs.xsane diff --git a/nixos/profiles/pipewire.nix b/nixos/profiles/pipewire.nix index 7ddaf6e..63ca1d6 100644 --- a/nixos/profiles/pipewire.nix +++ b/nixos/profiles/pipewire.nix @@ -12,7 +12,7 @@ with lib; # 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; diff --git a/nixpkgs/overlays.nix b/nixpkgs/overlays.nix index 05f9ad9..8683704 100644 --- a/nixpkgs/overlays.nix +++ b/nixpkgs/overlays.nix @@ -1,6 +1,6 @@ map import [ overlays/anydesk.nix - overlays/gcompris.nix + # overlays/gcompris.nix overlays/git-remote-gpg.nix overlays/irssi.nix overlays/modemmanager.nix diff --git a/nixpkgs/overlays/gcompris.nix b/nixpkgs/overlays/gcompris.nix index fc98360..7fe645c 100644 --- a/nixpkgs/overlays/gcompris.nix +++ b/nixpkgs/overlays/gcompris.nix @@ -1,5 +1,5 @@ finalPkgs: previousPkgs: { gcompris = previousPkgs.gcompris.overrideAttrs (previousAttrs: rec { - buildInputs = previousAttrs.buildInputs ++ [ finalPkgs.qt5.qtimageformats ]; + buildInputs = previousAttrs.buildInputs ++ [ finalPkgs.qt6.qtimageformats ]; }); } diff --git a/nixpkgs/overlays/irssi/irssi-connection-set-key.patch b/nixpkgs/overlays/irssi/irssi-connection-set-key.patch index b32ce97..ba273cc 100644 --- a/nixpkgs/overlays/irssi/irssi-connection-set-key.patch +++ b/nixpkgs/overlays/irssi/irssi-connection-set-key.patch @@ -56,6 +56,20 @@ index 30fc684..83733d0 100644 + 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/nixpkgs/patches.nix b/nixpkgs/patches.nix index aeae4ee..f93da90 100644 --- a/nixpkgs/patches.nix +++ b/nixpkgs/patches.nix @@ -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 { @@ -17,9 +22,11 @@ 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="; + } + */ ] diff --git a/nixpkgs/patches/openvpn/0001-nixos-netns-init-module-to-manage-network-namespaces.patch b/nixpkgs/patches/openvpn/0001-nixos-netns-init-module-to-manage-network-namespaces.patch index 8b6b9f2..c2fcabe 100644 --- a/nixpkgs/patches/openvpn/0001-nixos-netns-init-module-to-manage-network-namespaces.patch +++ b/nixpkgs/patches/openvpn/0001-nixos-netns-init-module-to-manage-network-namespaces.patch @@ -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 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 -index 783f20546af4..3df0004f5f82 100644 +index 79f5c22f5b..a350346754 100644 --- 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 @@ -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 -index 000000000000..8e37c2226848 +index 0000000000..eb05d0c0fd --- /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 -+ 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 +{ -+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 diff --git a/nixpkgs/patches/openvpn/0002-nixos-openvpn-add-netns-support.patch b/nixpkgs/patches/openvpn/0002-nixos-openvpn-add-netns-support.patch index fe933f7..0be1bba 100644 --- a/nixpkgs/patches/openvpn/0002-nixos-openvpn-add-netns-support.patch +++ b/nixpkgs/patches/openvpn/0002-nixos-openvpn-add-netns-support.patch @@ -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 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 -index 56b1f6f5ab8f..9805099789b1 100644 +index 0231e43447..d0a95e6e5a 100644 --- 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; @@ -21,31 +21,33 @@ index 56b1f6f5ab8f..9805099789b1 100644 + 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}" ( -+ 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 = { -+ up = script: ++ up = ++ script: + let + init = '' + export PATH=${PATH name} @@ -72,11 +74,9 @@ index 56b1f6f5ab8f..9805099789b1 100644 + 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 @@ -110,159 +110,166 @@ index 56b1f6f5ab8f..9805099789b1 100644 + 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} -+ set -eux ++ ${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" '' ++ ${init} ++ set -eux ++ export PATH=${PATH name} - 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} - ''; -+ 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 -+ 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 -+ 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 -+ 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_ -+ # 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_ ++ # 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} -+ ${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 -+ 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} -+ ''} -+ 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 { -@@ -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"; - 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"; @@ -270,7 +277,7 @@ index 56b1f6f5ab8f..9805099789b1 100644 serviceConfig.Type = "notify"; }; -@@ -97,30 +250,30 @@ in +@@ -109,30 +266,30 @@ in 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 != { }) { -- 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; - 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 index 0000000..943dc55 --- /dev/null +++ b/nixpkgs/patches/syncoid/0001-nixos-sanoid-add-utilities-useful-to-syncoid.patch @@ -0,0 +1,30 @@ +From 737f9dc0347e4c37316d219dee482dc298d9a6fb Mon Sep 17 00:00:00 2001 +From: Julien Moutinho +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 index 0000000..b0df633 --- /dev/null +++ b/nixpkgs/patches/syncoid/0002-nixos-syncoid-use-DynamicUser.patch @@ -0,0 +1,717 @@ +From 0e837ecd5ffb5e8366a5e2878838ba691b31cd99 Mon Sep 17 00:00:00 2001 +From: Julien Moutinho +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 +- ++ Permissions granted for the syncoid user for local source datasets. ++ See + 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 +- ++ Permissions granted for the syncoid user for local target datasets. ++ See + 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 + diff --git a/shell.nix b/shell.nix index 1840e0f..f772c96 100644 --- a/shell.nix +++ b/shell.nix @@ -29,7 +29,7 @@ pkgs.mkShell { ]; 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}