···40}
41```
4243-## Nix Store Partition {#sec-image-repart-store-partition}
0000004445You can define a partition that only contains the Nix store and then mount it
46under `/nix/store`. Because the `/nix/store` part of the paths is already
47-determined by the mount point, you have to set `stripNixStorePrefix = true;` so
48-that the prefix is stripped from the paths before copying them into the image.
4950```nix
51{
···54 image.repart.partitions = {
55 "store" = {
56 storePaths = [ config.system.build.toplevel ];
57- stripNixStorePrefix = true;
58 repartConfig = {
59 Type = "linux-generic";
60 Label = "nix-store";
00000000000000000000000000000000000061 # ...
62 };
63 };
···40}
41```
4243+## Nix Store Paths {#sec-image-repart-store-paths}
44+45+If you want to rewrite Nix store paths, e.g., to remove the `/nix/store` prefix
46+or to nest it below a parent path, you can do that through the
47+`nixStorePrefix` option.
48+49+### Nix Store Partition {#sec-image-repart-store-partition}
5051You can define a partition that only contains the Nix store and then mount it
52under `/nix/store`. Because the `/nix/store` part of the paths is already
53+determined by the mount point, you have to set `nixStorePrefix = "/"` so
54+that `/nix/store` is stripped from the paths before copying them into the image.
5556```nix
57{
···60 image.repart.partitions = {
61 "store" = {
62 storePaths = [ config.system.build.toplevel ];
63+ nixStorePrefix = "/";
64 repartConfig = {
65 Type = "linux-generic";
66 Label = "nix-store";
67+ # ...
68+ };
69+ };
70+ };
71+}
72+```
73+74+### Nix Store Subvolume {#sec-image-repart-store-subvolume}
75+76+Alternatively, you can create a Btrfs subvolume `/@nix-store` containing the
77+Nix store and mount it on `/nix/store`:
78+79+```nix
80+{
81+ fileSystems."/" = {
82+ device = "/dev/disk/by-partlabel/root";
83+ fsType = "btrfs";
84+ options = [ "subvol=/@" ];
85+ };
86+87+ fileSystems."/nix/store" = {
88+ device = "/dev/disk/by-partlabel/root";
89+ fsType = "btrfs";
90+ options = [ "subvol=/@nix-store" ];
91+ };
92+93+ image.repart.partitions = {
94+ "root" = {
95+ storePaths = [ config.system.build.toplevel ];
96+ nixStorePrefix = "/@nix-store";
97+ repartConfig = {
98+ Type = "root";
99+ Label = "root";
100+ Format = "btrfs";
101+ Subvolumes = "/@ /@nix-store";
102+ MakeDirectories = "/@ /@nix-store";
103 # ...
104 };
105 };
···363738def add_closure_to_definition(
39- definition: Path, closure: Path | None, strip_nix_store_prefix: bool | None
40) -> None:
41 """Add CopyFiles= instructions to a definition for all paths in the closure.
4243- If strip_nix_store_prefix is True, `/nix/store` is stripped from the target path.
44 """
45 if not closure:
46 return
···52 continue
5354 source = Path(line.strip())
55- target = str(source.relative_to("/nix/store/"))
56- target = f":/{target}" if strip_nix_store_prefix else ""
005758- copy_files_lines.append(f"CopyFiles={source}{target}\n")
5960 with open(definition, "a") as f:
61 f.writelines(copy_files_lines)
···102 add_contents_to_definition(definition, contents)
103104 closure = config.get("closure")
105- strip_nix_store_prefix = config.get("stripNixStorePrefix")
106- add_closure_to_definition(definition, closure, strip_nix_store_prefix)
107108 print(target_dir.absolute())
109
···363738def add_closure_to_definition(
39+ definition: Path, closure: Path | None, nix_store_prefix: str | None
40) -> None:
41 """Add CopyFiles= instructions to a definition for all paths in the closure.
4243+ Replace `/nix/store` with the value of nix_store_prefix.
44 """
45 if not closure:
46 return
···52 continue
5354 source = Path(line.strip())
55+ option = f"CopyFiles={source}"
56+ if nix_store_prefix:
57+ target = nix_store_prefix / source.relative_to("/nix/store/")
58+ option = f"{option}:{target}"
5960+ copy_files_lines.append(f"{option}\n")
6162 with open(definition, "a") as f:
63 f.writelines(copy_files_lines)
···104 add_contents_to_definition(definition, contents)
105106 closure = config.get("closure")
107+ nix_store_prefix = config.get("nixStorePrefix")
108+ add_closure_to_definition(definition, closure, nix_store_prefix)
109110 print(target_dir.absolute())
111
+86-69
nixos/modules/image/repart.nix
···1516 inherit (utils.systemdUtils.lib) GPTMaxLabelLength;
1718- partitionOptions = {
19- options = {
20- storePaths = lib.mkOption {
21- type = with lib.types; listOf path;
22- default = [ ];
23- description = "The store paths to include in the partition.";
24- };
002526- stripNixStorePrefix = lib.mkOption {
27- type = lib.types.bool;
28- default = false;
29- description = ''
30- Whether to strip `/nix/store/` from the store paths. This is useful
31- when you want to build a partition that only contains store paths and
32- is mounted under `/nix/store`.
33- '';
34- };
3536- contents = lib.mkOption {
37- type =
38- with lib.types;
39- attrsOf (submodule {
40- options = {
41- source = lib.mkOption {
42- type = types.path;
43- description = "Path of the source file.";
00000000000044 };
45- };
46- });
47- default = { };
48- example = lib.literalExpression ''
49- {
50- "/EFI/BOOT/BOOTX64.EFI".source =
51- "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
5253- "/loader/entries/nixos.conf".source = systemdBootEntry;
54- }
55- '';
56- description = "The contents to end up in the filesystem image.";
57- };
5859- repartConfig = lib.mkOption {
60- type =
61- with lib.types;
62- attrsOf (oneOf [
63- str
64- int
65- bool
66- (listOf str)
67- ]);
68- example = {
69- Type = "home";
70- SizeMinBytes = "512M";
71- SizeMaxBytes = "2G";
00000072 };
73- description = ''
74- Specify the repart options for a partiton as a structural setting.
75- See {manpage}`repart.d(5)`
76- for all available options.
77- '';
78 };
79 };
80- };
8182 mkfsOptionsToEnv =
83 opts:
···350 }
351 ) cfg.partitions;
352353- warnings = lib.filter (v: v != null) (
354 lib.mapAttrsToList (
355 fileName: partitionConfig:
356 let
···358 suggestedMaxLabelLength = GPTMaxLabelLength - 2;
359 labelLength = builtins.stringLength repartConfig.Label;
360 in
361- if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then
362- ''
363- The partition label '${repartConfig.Label}'
364- defined for '${fileName}' is ${toString labelLength} characters long.
365- The suggested maximum label length is ${toString suggestedMaxLabelLength}.
366367- If you use sytemd-sysupdate style A/B updates, this might
368- not leave enough space to increment the version number included in
369- the label in a future release. For example, if your label is
370- ${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
371- you're at version 9, you cannot increment this to 10.
372- ''
373- else
374- null
0000375 ) cfg.partitions
376 );
377 };
···1516 inherit (utils.systemdUtils.lib) GPTMaxLabelLength;
1718+ partitionOptions =
19+ { config, ... }:
20+ {
21+ options = {
22+ storePaths = lib.mkOption {
23+ type = with lib.types; listOf path;
24+ default = [ ];
25+ description = "The store paths to include in the partition.";
26+ };
2728+ # Superseded by `nixStorePrefix`. Unfortunately, `mkChangedOptionModule`
29+ # does not support submodules.
30+ stripNixStorePrefix = lib.mkOption {
31+ default = "_mkMergedOptionModule";
32+ visible = false;
33+ };
0003435+ nixStorePrefix = lib.mkOption {
36+ type = lib.types.path;
37+ default = "/nix/store";
38+ description = ''
39+ The prefix to use for store paths. Defaults to `/nix/store`. This is
40+ useful when you want to build a partition that only contains store
41+ paths and is mounted under `/nix/store` or if you want to create the
42+ store paths below a parent path (e.g., `/@nix/nix/store`).
43+ '';
44+ };
45+46+ contents = lib.mkOption {
47+ type =
48+ with lib.types;
49+ attrsOf (submodule {
50+ options = {
51+ source = lib.mkOption {
52+ type = types.path;
53+ description = "Path of the source file.";
54+ };
55 };
56+ });
57+ default = { };
58+ example = lib.literalExpression ''
59+ {
60+ "/EFI/BOOT/BOOTX64.EFI".source =
61+ "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
06263+ "/loader/entries/nixos.conf".source = systemdBootEntry;
64+ }
65+ '';
66+ description = "The contents to end up in the filesystem image.";
67+ };
6869+ repartConfig = lib.mkOption {
70+ type =
71+ with lib.types;
72+ attrsOf (oneOf [
73+ str
74+ int
75+ bool
76+ (listOf str)
77+ ]);
78+ example = {
79+ Type = "home";
80+ SizeMinBytes = "512M";
81+ SizeMaxBytes = "2G";
82+ };
83+ description = ''
84+ Specify the repart options for a partiton as a structural setting.
85+ See {manpage}`repart.d(5)`
86+ for all available options.
87+ '';
88 };
89+ };
90+91+ config = lib.mkIf (config.stripNixStorePrefix == true) {
92+ nixStorePrefix = "/";
093 };
94 };
09596 mkfsOptionsToEnv =
97 opts:
···364 }
365 ) cfg.partitions;
366367+ warnings = lib.flatten (
368 lib.mapAttrsToList (
369 fileName: partitionConfig:
370 let
···372 suggestedMaxLabelLength = GPTMaxLabelLength - 2;
373 labelLength = builtins.stringLength repartConfig.Label;
374 in
375+ lib.optional (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) ''
376+ The partition label '${repartConfig.Label}'
377+ defined for '${fileName}' is ${toString labelLength} characters long.
378+ The suggested maximum label length is ${toString suggestedMaxLabelLength}.
0379380+ If you use sytemd-sysupdate style A/B updates, this might
381+ not leave enough space to increment the version number included in
382+ the label in a future release. For example, if your label is
383+ ${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
384+ you're at version 9, you cannot increment this to 10.
385+ ''
386+ ++ lib.optional (partitionConfig.stripNixStorePrefix != "_mkMergedOptionModule") ''
387+ The option definition `image.repart.paritions.${fileName}.stripNixStorePrefix`
388+ has changed to `image.repart.paritions.${fileName}.nixStorePrefix` and now
389+ accepts the path to use as prefix directly. Use `nixStorePrefix = "/"` to
390+ achieve the same effect as setting `stripNixStorePrefix = true`.
391+ ''
392 ) cfg.partitions
393 );
394 };