]
},
"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"
}
},
"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"
}
},
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";
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 {
#pkgs.ubootTools # currently broken
#pkgs.unar # currently broken
pkgs.unzip
- pkgs.vbetool
+ #pkgs.vbetool
pkgs.wgetpaste
pkgs.xmlstarlet
pkgs.xsel
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 = {
pkgs.direnv
pkgs.dislocker
pkgs.dmidecode
- pkgs.dstat
+ pkgs.dool
pkgs.dust
pkgs.e2fsprogs
pkgs.efitools
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
);
};
{ 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
{
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 = {
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
pkgs.calibre
#pkgs.zotero
pkgs.evince
- pkgs.marble
+ pkgs.kdePackages.marble
pkgs.gcompris
pkgs.frozen-bubble
pkgs.neverball
pkgs.calibre
pkgs.zotero
pkgs.evince
- pkgs.marble
+ pkgs.kdePackages.marble
pkgs.gcompris
pkgs.frozen-bubble
pkgs.neverball
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
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
# 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;
map import [
overlays/anydesk.nix
- overlays/gcompris.nix
+ # overlays/gcompris.nix
overlays/git-remote-gpg.nix
overlays/irssi.nix
overlays/modemmanager.nix
finalPkgs: previousPkgs: {
gcompris = previousPkgs.gcompris.overrideAttrs (previousAttrs: rec {
- buildInputs = previousAttrs.buildInputs ++ [ finalPkgs.qt5.qtimageformats ];
+ buildInputs = previousAttrs.buildInputs ++ [ finalPkgs.qt6.qtimageformats ];
});
}
+ 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
[
- {
- 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
{
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=";
+ }
+ */
]
-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 +
- 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
./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
-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
---
- 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;
+ 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}
+ 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
+ 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_<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}
-+ ${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";
serviceConfig.Type = "notify";
};
-@@ -97,30 +250,30 @@ in
+@@ -109,30 +266,30 @@ in
example = literalExpression ''
{
server = {
};
}
'';
-@@ -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
--- /dev/null
+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
+
--- /dev/null
+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
+
];
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}