···702702 option.
703703 </para>
704704 </listitem>
705705+ <listitem>
706706+ <para>
707707+ The
708708+ <link xlink:href="options.html#opt-services.syncoid.enable">services.syncoid.enable</link>
709709+ module now properly drops ZFS permissions after usage. Before
710710+ it delegated permissions to whole pools instead of datasets
711711+ and didn’t clean up after execution. You can manually look
712712+ this up for your pools by running
713713+ <literal>zfs allow your-pool-name</literal> and use
714714+ <literal>zfs unallow syncoid your-pool-name</literal> to clean
715715+ this up.
716716+ </para>
717717+ </listitem>
705718 </itemizedlist>
706719 </section>
707720</section>
+2
nixos/doc/manual/release-notes/rl-2111.section.md
···183183 - NSS modules which should come after `dns` should use mkAfter.
184184185185- The [networking.wireless.iwd](options.html#opt-networking.wireless.iwd.enable) module has a new [networking.wireless.iwd.settings](options.html#opt-networking.wireless.iwd.settings) option.
186186+187187+- The [services.syncoid.enable](options.html#opt-services.syncoid.enable) module now properly drops ZFS permissions after usage. Before it delegated permissions to whole pools instead of datasets and didn't clean up after execution. You can manually look this up for your pools by running `zfs allow your-pool-name` and use `zfs unallow syncoid your-pool-name` to clean this up.
+107-96
nixos/modules/services/backup/sanoid.nix
···5252 use_template = mkOption {
5353 description = "Names of the templates to use for this dataset.";
5454 type = types.listOf (types.enum (attrNames cfg.templates));
5555- default = [];
5555+ default = [ ];
5656 };
5757 useTemplate = use_template;
5858···7070 processChildrenOnly = process_children_only;
7171 };
72727373- # Extract pool names from configured datasets
7474- pools = unique (map (d: head (builtins.match "([^/]+).*" d)) (attrNames cfg.datasets));
7373+ # Extract unique dataset names
7474+ datasets = unique (attrNames cfg.datasets);
75757676- configFile = let
7777- mkValueString = v:
7878- if builtins.isList v then concatStringsSep "," v
7979- else generators.mkValueStringDefault {} v;
7676+ # Function to build "zfs allow" and "zfs unallow" commands for the
7777+ # filesystems we've delegated permissions to.
7878+ buildAllowCommand = zfsAction: permissions: dataset: lib.escapeShellArgs [
7979+ # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
8080+ "-+/run/booted-system/sw/bin/zfs"
8181+ zfsAction
8282+ "sanoid"
8383+ (concatStringsSep "," permissions)
8484+ dataset
8585+ ];
80868181- mkKeyValue = k: v: if v == null then ""
8282- else if k == "processChildrenOnly" then ""
8383- else if k == "useTemplate" then ""
8484- else generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
8585- in generators.toINI { inherit mkKeyValue; } cfg.settings;
8787+ configFile =
8888+ let
8989+ mkValueString = v:
9090+ if builtins.isList v then concatStringsSep "," v
9191+ else generators.mkValueStringDefault { } v;
86928787-in {
9393+ mkKeyValue = k: v:
9494+ if v == null then ""
9595+ else if k == "processChildrenOnly" then ""
9696+ else if k == "useTemplate" then ""
9797+ else generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
9898+ in
9999+ generators.toINI { inherit mkKeyValue; } cfg.settings;
881008989- # Interface
101101+in
102102+{
901039191- options.services.sanoid = {
9292- enable = mkEnableOption "Sanoid ZFS snapshotting service";
104104+ # Interface
931059494- interval = mkOption {
9595- type = types.str;
9696- default = "hourly";
9797- example = "daily";
9898- description = ''
9999- Run sanoid at this interval. The default is to run hourly.
106106+ options.services.sanoid = {
107107+ enable = mkEnableOption "Sanoid ZFS snapshotting service";
100108101101- The format is described in
102102- <citerefentry><refentrytitle>systemd.time</refentrytitle>
103103- <manvolnum>7</manvolnum></citerefentry>.
104104- '';
105105- };
109109+ interval = mkOption {
110110+ type = types.str;
111111+ default = "hourly";
112112+ example = "daily";
113113+ description = ''
114114+ Run sanoid at this interval. The default is to run hourly.
106115107107- datasets = mkOption {
108108- type = types.attrsOf (types.submodule ({config, options, ...}: {
109109- freeformType = datasetSettingsType;
110110- options = commonOptions // datasetOptions;
111111- config.use_template = mkAliasDefinitions (mkDefault options.useTemplate or {});
112112- config.process_children_only = mkAliasDefinitions (mkDefault options.processChildrenOnly or {});
113113- }));
114114- default = {};
115115- description = "Datasets to snapshot.";
116116- };
116116+ The format is described in
117117+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
118118+ <manvolnum>7</manvolnum></citerefentry>.
119119+ '';
120120+ };
121121+122122+ datasets = mkOption {
123123+ type = types.attrsOf (types.submodule ({ config, options, ... }: {
124124+ freeformType = datasetSettingsType;
125125+ options = commonOptions // datasetOptions;
126126+ config.use_template = mkAliasDefinitions (mkDefault options.useTemplate or { });
127127+ config.process_children_only = mkAliasDefinitions (mkDefault options.processChildrenOnly or { });
128128+ }));
129129+ default = { };
130130+ description = "Datasets to snapshot.";
131131+ };
117132118118- templates = mkOption {
119119- type = types.attrsOf (types.submodule {
120120- freeformType = datasetSettingsType;
121121- options = commonOptions;
122122- });
123123- default = {};
124124- description = "Templates for datasets.";
125125- };
133133+ templates = mkOption {
134134+ type = types.attrsOf (types.submodule {
135135+ freeformType = datasetSettingsType;
136136+ options = commonOptions;
137137+ });
138138+ default = { };
139139+ description = "Templates for datasets.";
140140+ };
126141127127- settings = mkOption {
128128- type = types.attrsOf datasetSettingsType;
129129- description = ''
130130- Free-form settings written directly to the config file. See
131131- <link xlink:href="https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf"/>
132132- for allowed values.
133133- '';
134134- };
142142+ settings = mkOption {
143143+ type = types.attrsOf datasetSettingsType;
144144+ description = ''
145145+ Free-form settings written directly to the config file. See
146146+ <link xlink:href="https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf"/>
147147+ for allowed values.
148148+ '';
149149+ };
135150136136- extraArgs = mkOption {
137137- type = types.listOf types.str;
138138- default = [];
139139- example = [ "--verbose" "--readonly" "--debug" ];
140140- description = ''
141141- Extra arguments to pass to sanoid. See
142142- <link xlink:href="https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options"/>
143143- for allowed options.
144144- '';
145145- };
151151+ extraArgs = mkOption {
152152+ type = types.listOf types.str;
153153+ default = [ ];
154154+ example = [ "--verbose" "--readonly" "--debug" ];
155155+ description = ''
156156+ Extra arguments to pass to sanoid. See
157157+ <link xlink:href="https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options"/>
158158+ for allowed options.
159159+ '';
146160 };
161161+ };
147162148148- # Implementation
163163+ # Implementation
149164150150- config = mkIf cfg.enable {
151151- services.sanoid.settings = mkMerge [
152152- (mapAttrs' (d: v: nameValuePair ("template_" + d) v) cfg.templates)
153153- (mapAttrs (d: v: v) cfg.datasets)
154154- ];
165165+ config = mkIf cfg.enable {
166166+ services.sanoid.settings = mkMerge [
167167+ (mapAttrs' (d: v: nameValuePair ("template_" + d) v) cfg.templates)
168168+ (mapAttrs (d: v: v) cfg.datasets)
169169+ ];
155170156156- systemd.services.sanoid = {
157157- description = "Sanoid snapshot service";
158158- serviceConfig = {
159159- ExecStartPre = map (pool: lib.escapeShellArgs [
160160- "+/run/booted-system/sw/bin/zfs" "allow"
161161- "sanoid" "snapshot,mount,destroy" pool
162162- ]) pools;
163163- ExecStart = lib.escapeShellArgs ([
164164- "${pkgs.sanoid}/bin/sanoid"
165165- "--cron"
166166- "--configdir" (pkgs.writeTextDir "sanoid.conf" configFile)
167167- ] ++ cfg.extraArgs);
168168- ExecStopPost = map (pool: lib.escapeShellArgs [
169169- "+/run/booted-system/sw/bin/zfs" "unallow" "sanoid" pool
170170- ]) pools;
171171- User = "sanoid";
172172- Group = "sanoid";
173173- DynamicUser = true;
174174- RuntimeDirectory = "sanoid";
175175- CacheDirectory = "sanoid";
176176- };
177177- # Prevents missing snapshots during DST changes
178178- environment.TZ = "UTC";
179179- after = [ "zfs.target" ];
180180- startAt = cfg.interval;
171171+ systemd.services.sanoid = {
172172+ description = "Sanoid snapshot service";
173173+ serviceConfig = {
174174+ ExecStartPre = (map (buildAllowCommand "allow" [ "snapshot" "mount" "destroy" ]) datasets);
175175+ ExecStopPost = (map (buildAllowCommand "unallow" [ "snapshot" "mount" "destroy" ]) datasets);
176176+ ExecStart = lib.escapeShellArgs ([
177177+ "${pkgs.sanoid}/bin/sanoid"
178178+ "--cron"
179179+ "--configdir"
180180+ (pkgs.writeTextDir "sanoid.conf" configFile)
181181+ ] ++ cfg.extraArgs);
182182+ User = "sanoid";
183183+ Group = "sanoid";
184184+ DynamicUser = true;
185185+ RuntimeDirectory = "sanoid";
186186+ CacheDirectory = "sanoid";
181187 };
188188+ # Prevents missing snapshots during DST changes
189189+ environment.TZ = "UTC";
190190+ after = [ "zfs.target" ];
191191+ startAt = cfg.interval;
182192 };
193193+ };
183194184184- meta.maintainers = with maintainers; [ lopsided98 ];
185185- }
195195+ meta.maintainers = with maintainers; [ lopsided98 ];
196196+}
+210-186
nixos/modules/services/backup/syncoid.nix
···55let
66 cfg = config.services.syncoid;
7788- # Extract the pool name of a local dataset (any dataset not containing "@")
99- localPoolName = d: optionals (d != null) (
1010- let m = builtins.match "([^/@]+)[^@]*" d; in
1111- optionals (m != null) m);
88+ # Extract local dasaset names (so no datasets containing "@")
99+ localDatasetName = d: optionals (d != null) (
1010+ let m = builtins.match "([^/@]+[^@]*)" d; in
1111+ optionals (m != null) m
1212+ );
12131314 # Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
1415 escapeUnitName = name:
1516 lib.concatMapStrings (s: if lib.isList s then "-" else s)
1616- (builtins.split "[^a-zA-Z0-9_.\\-]+" name);
1717-in {
1717+ (builtins.split "[^a-zA-Z0-9_.\\-]+" name);
18181919- # Interface
1919+ # Function to build "zfs allow" and "zfs unallow" commands for the
2020+ # filesystems we've delegated permissions to.
2121+ buildAllowCommand = zfsAction: permissions: dataset: lib.escapeShellArgs [
2222+ # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
2323+ "-+/run/booted-system/sw/bin/zfs"
2424+ zfsAction
2525+ cfg.user
2626+ (concatStringsSep "," permissions)
2727+ dataset
2828+ ];
2929+in
3030+{
20312121- options.services.syncoid = {
2222- enable = mkEnableOption "Syncoid ZFS synchronization service";
3232+ # Interface
23332424- interval = mkOption {
2525- type = types.str;
2626- default = "hourly";
2727- example = "*-*-* *:15:00";
2828- description = ''
2929- Run syncoid at this interval. The default is to run hourly.
3434+ options.services.syncoid = {
3535+ enable = mkEnableOption "Syncoid ZFS synchronization service";
30363131- The format is described in
3232- <citerefentry><refentrytitle>systemd.time</refentrytitle>
3333- <manvolnum>7</manvolnum></citerefentry>.
3434- '';
3535- };
3737+ interval = mkOption {
3838+ type = types.str;
3939+ default = "hourly";
4040+ example = "*-*-* *:15:00";
4141+ description = ''
4242+ Run syncoid at this interval. The default is to run hourly.
36433737- user = mkOption {
3838- type = types.str;
3939- default = "syncoid";
4040- example = "backup";
4141- description = ''
4242- The user for the service. ZFS privilege delegation will be
4343- automatically configured for any local pools used by syncoid if this
4444- option is set to a user other than root. The user will be given the
4545- "hold" and "send" privileges on any pool that has datasets being sent
4646- and the "create", "mount", "receive", and "rollback" privileges on
4747- any pool that has datasets being received.
4848- '';
4949- };
4444+ The format is described in
4545+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
4646+ <manvolnum>7</manvolnum></citerefentry>.
4747+ '';
4848+ };
50495151- group = mkOption {
5252- type = types.str;
5353- default = "syncoid";
5454- example = "backup";
5555- description = "The group for the service.";
5656- };
5050+ user = mkOption {
5151+ type = types.str;
5252+ default = "syncoid";
5353+ example = "backup";
5454+ description = ''
5555+ The user for the service. ZFS privilege delegation will be
5656+ automatically configured for any local pools used by syncoid if this
5757+ option is set to a user other than root. The user will be given the
5858+ "hold" and "send" privileges on any pool that has datasets being sent
5959+ and the "create", "mount", "receive", and "rollback" privileges on
6060+ any pool that has datasets being received.
6161+ '';
6262+ };
57635858- sshKey = mkOption {
5959- type = types.nullOr types.path;
6060- # Prevent key from being copied to store
6161- apply = mapNullable toString;
6262- default = null;
6363- description = ''
6464- SSH private key file to use to login to the remote system. Can be
6565- overridden in individual commands.
6666- '';
6767- };
6464+ group = mkOption {
6565+ type = types.str;
6666+ default = "syncoid";
6767+ example = "backup";
6868+ description = "The group for the service.";
6969+ };
68706969- commonArgs = mkOption {
7070- type = types.listOf types.str;
7171- default = [];
7272- example = [ "--no-sync-snap" ];
7373- description = ''
7474- Arguments to add to every syncoid command, unless disabled for that
7575- command. See
7676- <link xlink:href="https://github.com/jimsalterjrs/sanoid/#syncoid-command-line-options"/>
7777- for available options.
7878- '';
7979- };
7171+ sshKey = mkOption {
7272+ type = types.nullOr types.path;
7373+ # Prevent key from being copied to store
7474+ apply = mapNullable toString;
7575+ default = null;
7676+ description = ''
7777+ SSH private key file to use to login to the remote system. Can be
7878+ overridden in individual commands.
7979+ '';
8080+ };
80818181- service = mkOption {
8282- type = types.attrs;
8383- default = {};
8484- description = ''
8585- Systemd configuration common to all syncoid services.
8686- '';
8787- };
8282+ commonArgs = mkOption {
8383+ type = types.listOf types.str;
8484+ default = [ ];
8585+ example = [ "--no-sync-snap" ];
8686+ description = ''
8787+ Arguments to add to every syncoid command, unless disabled for that
8888+ command. See
8989+ <link xlink:href="https://github.com/jimsalterjrs/sanoid/#syncoid-command-line-options"/>
9090+ for available options.
9191+ '';
9292+ };
88938989- commands = mkOption {
9090- type = types.attrsOf (types.submodule ({ name, ... }: {
9191- options = {
9292- source = mkOption {
9393- type = types.str;
9494- example = "pool/dataset";
9595- description = ''
9696- Source ZFS dataset. Can be either local or remote. Defaults to
9797- the attribute name.
9898- '';
9999- };
9494+ service = mkOption {
9595+ type = types.attrs;
9696+ default = { };
9797+ description = ''
9898+ Systemd configuration common to all syncoid services.
9999+ '';
100100+ };
100101101101- target = mkOption {
102102- type = types.str;
103103- example = "user@server:pool/dataset";
104104- description = ''
105105- Target ZFS dataset. Can be either local
106106- (<replaceable>pool/dataset</replaceable>) or remote
107107- (<replaceable>user@server:pool/dataset</replaceable>).
108108- '';
109109- };
102102+ commands = mkOption {
103103+ type = types.attrsOf (types.submodule ({ name, ... }: {
104104+ options = {
105105+ source = mkOption {
106106+ type = types.str;
107107+ example = "pool/dataset";
108108+ description = ''
109109+ Source ZFS dataset. Can be either local or remote. Defaults to
110110+ the attribute name.
111111+ '';
112112+ };
110113111111- recursive = mkEnableOption ''the transfer of child datasets'';
114114+ target = mkOption {
115115+ type = types.str;
116116+ example = "user@server:pool/dataset";
117117+ description = ''
118118+ Target ZFS dataset. Can be either local
119119+ (<replaceable>pool/dataset</replaceable>) or remote
120120+ (<replaceable>user@server:pool/dataset</replaceable>).
121121+ '';
122122+ };
112123113113- sshKey = mkOption {
114114- type = types.nullOr types.path;
115115- # Prevent key from being copied to store
116116- apply = mapNullable toString;
117117- description = ''
118118- SSH private key file to use to login to the remote system.
119119- Defaults to <option>services.syncoid.sshKey</option> option.
120120- '';
121121- };
124124+ recursive = mkEnableOption ''the transfer of child datasets'';
122125123123- sendOptions = mkOption {
124124- type = types.separatedString " ";
125125- default = "";
126126- example = "Lc e";
127127- description = ''
128128- Advanced options to pass to zfs send. Options are specified
129129- without their leading dashes and separated by spaces.
130130- '';
131131- };
126126+ sshKey = mkOption {
127127+ type = types.nullOr types.path;
128128+ # Prevent key from being copied to store
129129+ apply = mapNullable toString;
130130+ description = ''
131131+ SSH private key file to use to login to the remote system.
132132+ Defaults to <option>services.syncoid.sshKey</option> option.
133133+ '';
134134+ };
132135133133- recvOptions = mkOption {
134134- type = types.separatedString " ";
135135- default = "";
136136- example = "ux recordsize o compression=lz4";
137137- description = ''
138138- Advanced options to pass to zfs recv. Options are specified
139139- without their leading dashes and separated by spaces.
140140- '';
141141- };
136136+ sendOptions = mkOption {
137137+ type = types.separatedString " ";
138138+ default = "";
139139+ example = "Lc e";
140140+ description = ''
141141+ Advanced options to pass to zfs send. Options are specified
142142+ without their leading dashes and separated by spaces.
143143+ '';
144144+ };
142145143143- useCommonArgs = mkOption {
144144- type = types.bool;
145145- default = true;
146146- description = ''
147147- Whether to add the configured common arguments to this command.
148148- '';
149149- };
146146+ recvOptions = mkOption {
147147+ type = types.separatedString " ";
148148+ default = "";
149149+ example = "ux recordsize o compression=lz4";
150150+ description = ''
151151+ Advanced options to pass to zfs recv. Options are specified
152152+ without their leading dashes and separated by spaces.
153153+ '';
154154+ };
150155151151- service = mkOption {
152152- type = types.attrs;
153153- default = {};
154154- description = ''
155155- Systemd configuration specific to this syncoid service.
156156- '';
157157- };
156156+ useCommonArgs = mkOption {
157157+ type = types.bool;
158158+ default = true;
159159+ description = ''
160160+ Whether to add the configured common arguments to this command.
161161+ '';
162162+ };
158163159159- extraArgs = mkOption {
160160- type = types.listOf types.str;
161161- default = [];
162162- example = [ "--sshport 2222" ];
163163- description = "Extra syncoid arguments for this command.";
164164- };
164164+ service = mkOption {
165165+ type = types.attrs;
166166+ default = { };
167167+ description = ''
168168+ Systemd configuration specific to this syncoid service.
169169+ '';
165170 };
166166- config = {
167167- source = mkDefault name;
168168- sshKey = mkDefault cfg.sshKey;
171171+172172+ extraArgs = mkOption {
173173+ type = types.listOf types.str;
174174+ default = [ ];
175175+ example = [ "--sshport 2222" ];
176176+ description = "Extra syncoid arguments for this command.";
169177 };
170170- }));
171171- default = {};
172172- example = literalExample ''
173173- {
174174- "pool/test".target = "root@target:pool/test";
175175- }
176176- '';
177177- description = "Syncoid commands to run.";
178178- };
178178+ };
179179+ config = {
180180+ source = mkDefault name;
181181+ sshKey = mkDefault cfg.sshKey;
182182+ };
183183+ }));
184184+ default = { };
185185+ example = literalExample ''
186186+ {
187187+ "pool/test".target = "root@target:pool/test";
188188+ }
189189+ '';
190190+ description = "Syncoid commands to run.";
179191 };
192192+ };
180193181181- # Implementation
194194+ # Implementation
182195183183- config = mkIf cfg.enable {
184184- users = {
185185- users = mkIf (cfg.user == "syncoid") {
186186- syncoid = {
187187- group = cfg.group;
188188- isSystemUser = true;
189189- # For syncoid to be able to create /var/lib/syncoid/.ssh/
190190- # and to use custom ssh_config or known_hosts.
191191- home = "/var/lib/syncoid";
192192- createHome = false;
193193- };
194194- };
195195- groups = mkIf (cfg.group == "syncoid") {
196196- syncoid = {};
196196+ config = mkIf cfg.enable {
197197+ users = {
198198+ users = mkIf (cfg.user == "syncoid") {
199199+ syncoid = {
200200+ group = cfg.group;
201201+ isSystemUser = true;
202202+ # For syncoid to be able to create /var/lib/syncoid/.ssh/
203203+ # and to use custom ssh_config or known_hosts.
204204+ home = "/var/lib/syncoid";
205205+ createHome = false;
197206 };
198207 };
208208+ groups = mkIf (cfg.group == "syncoid") {
209209+ syncoid = { };
210210+ };
211211+ };
199212200200- systemd.services = mapAttrs' (name: c:
213213+ systemd.services = mapAttrs'
214214+ (name: c:
201215 nameValuePair "syncoid-${escapeUnitName name}" (mkMerge [
202202- { description = "Syncoid ZFS synchronization from ${c.source} to ${c.target}";
216216+ {
217217+ description = "Syncoid ZFS synchronization from ${c.source} to ${c.target}";
203218 after = [ "zfs.target" ];
204219 startAt = cfg.interval;
205220 # syncoid may need zpool to get feature@extensible_dataset
206221 path = [ "/run/booted-system/sw/bin/" ];
207222 serviceConfig = {
208223 ExecStartPre =
209209- map (pool: lib.escapeShellArgs [
210210- "+/run/booted-system/sw/bin/zfs" "allow"
211211- cfg.user "bookmark,hold,send,snapshot,destroy" pool
212212- # Permissions snapshot and destroy are in case --no-sync-snap is not used
213213- ]) (localPoolName c.source) ++
214214- map (pool: lib.escapeShellArgs [
215215- "+/run/booted-system/sw/bin/zfs" "allow"
216216- cfg.user "create,mount,receive,rollback" pool
217217- ]) (localPoolName c.target);
224224+ # Permissions snapshot and destroy are in case --no-sync-snap is not used
225225+ (map (buildAllowCommand "allow" [ "bookmark" "hold" "send" "snapshot" "destroy" ]) (localDatasetName c.source)) ++
226226+ (map (buildAllowCommand "allow" [ "create" "mount" "receive" "rollback" ]) (localDatasetName c.target));
227227+ ExecStopPost =
228228+ # Permissions snapshot and destroy are in case --no-sync-snap is not used
229229+ (map (buildAllowCommand "unallow" [ "bookmark" "hold" "send" "snapshot" "destroy" ]) (localDatasetName c.source)) ++
230230+ (map (buildAllowCommand "unallow" [ "create" "mount" "receive" "rollback" ]) (localDatasetName c.target));
218231 ExecStart = lib.escapeShellArgs ([ "${pkgs.sanoid}/bin/syncoid" ]
219232 ++ optionals c.useCommonArgs cfg.commonArgs
220233 ++ optional c.recursive "-r"
221234 ++ optionals (c.sshKey != null) [ "--sshkey" c.sshKey ]
222235 ++ c.extraArgs
223223- ++ [ "--sendoptions" c.sendOptions
224224- "--recvoptions" c.recvOptions
225225- "--no-privilege-elevation"
226226- c.source c.target
227227- ]);
236236+ ++ [
237237+ "--sendoptions"
238238+ c.sendOptions
239239+ "--recvoptions"
240240+ c.recvOptions
241241+ "--no-privilege-elevation"
242242+ c.source
243243+ c.target
244244+ ]);
228245 User = cfg.user;
229246 Group = cfg.group;
230247 StateDirectory = [ "syncoid" ];
···240257 # systemd-analyze security | grep syncoid-'*'
241258 AmbientCapabilities = "";
242259 CapabilityBoundingSet = "";
243243- DeviceAllow = ["/dev/zfs"];
260260+ DeviceAllow = [ "/dev/zfs" ];
244261 LockPersonality = true;
245262 MemoryDenyWriteExecute = true;
246263 NoNewPrivileges = true;
···266283 BindPaths = [ "/dev/zfs" ];
267284 BindReadOnlyPaths = [ builtins.storeDir "/etc" "/run" "/bin/sh" ];
268285 # Avoid useless mounting of RootDirectory= in the own RootDirectory= of ExecStart='s mount namespace.
269269- InaccessiblePaths = ["-+/run/syncoid/${escapeUnitName name}"];
286286+ InaccessiblePaths = [ "-+/run/syncoid/${escapeUnitName name}" ];
270287 MountAPIVFS = true;
271288 # Create RootDirectory= in the host's mount namespace.
272289 RuntimeDirectory = [ "syncoid/${escapeUnitName name}" ];
···277294 # perf stat -x, 2>perf.log -e 'syscalls:sys_enter_*' syncoid …
278295 # awk >perf.syscalls -F "," '$1 > 0 {sub("syscalls:sys_enter_","",$3); print $3}' perf.log
279296 # 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 ' '
280280- "~@aio" "~@chown" "~@keyring" "~@memlock" "~@privileged"
281281- "~@resources" "~@setuid" "~@sync" "~@timer"
297297+ "~@aio"
298298+ "~@chown"
299299+ "~@keyring"
300300+ "~@memlock"
301301+ "~@privileged"
302302+ "~@resources"
303303+ "~@setuid"
304304+ "~@timer"
282305 ];
283306 SystemCallArchitectures = "native";
284307 # This is for BindPaths= and BindReadOnlyPaths=
···288311 }
289312 cfg.service
290313 c.service
291291- ])) cfg.commands;
292292- };
314314+ ]))
315315+ cfg.commands;
316316+ };
293317294294- meta.maintainers = with maintainers; [ julm lopsided98 ];
295295- }
318318+ meta.maintainers = with maintainers; [ julm lopsided98 ];
319319+}
+12
nixos/tests/sanoid.nix
···8585 "chown -R syncoid:syncoid /var/lib/syncoid/",
8686 )
87878888+ assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set before snapshotting"
8989+ assert len(source.succeed("zfs allow pool/sanoid")) == 0, "Sanoid dataset shouldn't have delegated permissions set before snapshotting"
9090+ assert len(source.succeed("zfs allow pool/syncoid")) == 0, "Syncoid dataset shouldn't have delegated permissions set before snapshotting"
9191+8892 # Take snapshot with sanoid
8993 source.succeed("touch /mnt/pool/sanoid/test.txt")
9094 source.systemctl("start --wait sanoid.service")
91959696+ assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set after snapshotting"
9797+ assert len(source.succeed("zfs allow pool/sanoid")) == 0, "Sanoid dataset shouldn't have delegated permissions set after snapshotting"
9898+ assert len(source.succeed("zfs allow pool/syncoid")) == 0, "Syncoid dataset shouldn't have delegated permissions set after snapshotting"
9999+92100 # Sync snapshots
93101 target.wait_for_open_port(22)
94102 source.succeed("touch /mnt/pool/syncoid/test.txt")
···96104 target.succeed("cat /mnt/pool/sanoid/test.txt")
97105 source.systemctl("start --wait syncoid-pool-syncoid.service")
98106 target.succeed("cat /mnt/pool/syncoid/test.txt")
107107+108108+ assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set after syncing snapshots"
109109+ assert len(source.succeed("zfs allow pool/sanoid")) == 0, "Sanoid dataset shouldn't have delegated permissions set after syncing snapshots"
110110+ assert len(source.succeed("zfs allow pool/syncoid")) == 0, "Syncoid dataset shouldn't have delegated permissions set after syncing snapshots"
99111 '';
100112})