···4040}
4141```
42424343-## Nix Store Partition {#sec-image-repart-store-partition}
4343+## Nix Store Paths {#sec-image-repart-store-paths}
4444+4545+If you want to rewrite Nix store paths, e.g., to remove the `/nix/store` prefix
4646+or to nest it below a parent path, you can do that through the
4747+`nixStorePrefix` option.
4848+4949+### Nix Store Partition {#sec-image-repart-store-partition}
44504551You can define a partition that only contains the Nix store and then mount it
4652under `/nix/store`. Because the `/nix/store` part of the paths is already
4747-determined by the mount point, you have to set `stripNixStorePrefix = true;` so
4848-that the prefix is stripped from the paths before copying them into the image.
5353+determined by the mount point, you have to set `nixStorePrefix = "/"` so
5454+that `/nix/store` is stripped from the paths before copying them into the image.
49555056```nix
5157{
···5460 image.repart.partitions = {
5561 "store" = {
5662 storePaths = [ config.system.build.toplevel ];
5757- stripNixStorePrefix = true;
6363+ nixStorePrefix = "/";
5864 repartConfig = {
5965 Type = "linux-generic";
6066 Label = "nix-store";
6767+ # ...
6868+ };
6969+ };
7070+ };
7171+}
7272+```
7373+7474+### Nix Store Subvolume {#sec-image-repart-store-subvolume}
7575+7676+Alternatively, you can create a Btrfs subvolume `/@nix-store` containing the
7777+Nix store and mount it on `/nix/store`:
7878+7979+```nix
8080+{
8181+ fileSystems."/" = {
8282+ device = "/dev/disk/by-partlabel/root";
8383+ fsType = "btrfs";
8484+ options = [ "subvol=/@" ];
8585+ };
8686+8787+ fileSystems."/nix/store" = {
8888+ device = "/dev/disk/by-partlabel/root";
8989+ fsType = "btrfs";
9090+ options = [ "subvol=/@nix-store" ];
9191+ };
9292+9393+ image.repart.partitions = {
9494+ "root" = {
9595+ storePaths = [ config.system.build.toplevel ];
9696+ nixStorePrefix = "/@nix-store";
9797+ repartConfig = {
9898+ Type = "root";
9999+ Label = "root";
100100+ Format = "btrfs";
101101+ Subvolumes = "/@ /@nix-store";
102102+ MakeDirectories = "/@ /@nix-store";
61103 # ...
62104 };
63105 };
···363637373838def add_closure_to_definition(
3939- definition: Path, closure: Path | None, strip_nix_store_prefix: bool | None
3939+ definition: Path, closure: Path | None, nix_store_prefix: str | None
4040) -> None:
4141 """Add CopyFiles= instructions to a definition for all paths in the closure.
42424343- If strip_nix_store_prefix is True, `/nix/store` is stripped from the target path.
4343+ Replace `/nix/store` with the value of nix_store_prefix.
4444 """
4545 if not closure:
4646 return
···5252 continue
53535454 source = Path(line.strip())
5555- target = str(source.relative_to("/nix/store/"))
5656- target = f":/{target}" if strip_nix_store_prefix else ""
5555+ option = f"CopyFiles={source}"
5656+ if nix_store_prefix:
5757+ target = nix_store_prefix / source.relative_to("/nix/store/")
5858+ option = f"{option}:{target}"
57595858- copy_files_lines.append(f"CopyFiles={source}{target}\n")
6060+ copy_files_lines.append(f"{option}\n")
59616062 with open(definition, "a") as f:
6163 f.writelines(copy_files_lines)
···102104 add_contents_to_definition(definition, contents)
103105104106 closure = config.get("closure")
105105- strip_nix_store_prefix = config.get("stripNixStorePrefix")
106106- add_closure_to_definition(definition, closure, strip_nix_store_prefix)
107107+ nix_store_prefix = config.get("nixStorePrefix")
108108+ add_closure_to_definition(definition, closure, nix_store_prefix)
107109108110 print(target_dir.absolute())
109111
+86-69
nixos/modules/image/repart.nix
···15151616 inherit (utils.systemdUtils.lib) GPTMaxLabelLength;
17171818- partitionOptions = {
1919- options = {
2020- storePaths = lib.mkOption {
2121- type = with lib.types; listOf path;
2222- default = [ ];
2323- description = "The store paths to include in the partition.";
2424- };
1818+ partitionOptions =
1919+ { config, ... }:
2020+ {
2121+ options = {
2222+ storePaths = lib.mkOption {
2323+ type = with lib.types; listOf path;
2424+ default = [ ];
2525+ description = "The store paths to include in the partition.";
2626+ };
25272626- stripNixStorePrefix = lib.mkOption {
2727- type = lib.types.bool;
2828- default = false;
2929- description = ''
3030- Whether to strip `/nix/store/` from the store paths. This is useful
3131- when you want to build a partition that only contains store paths and
3232- is mounted under `/nix/store`.
3333- '';
3434- };
2828+ # Superseded by `nixStorePrefix`. Unfortunately, `mkChangedOptionModule`
2929+ # does not support submodules.
3030+ stripNixStorePrefix = lib.mkOption {
3131+ default = "_mkMergedOptionModule";
3232+ visible = false;
3333+ };
35343636- contents = lib.mkOption {
3737- type =
3838- with lib.types;
3939- attrsOf (submodule {
4040- options = {
4141- source = lib.mkOption {
4242- type = types.path;
4343- description = "Path of the source file.";
3535+ nixStorePrefix = lib.mkOption {
3636+ type = lib.types.path;
3737+ default = "/nix/store";
3838+ description = ''
3939+ The prefix to use for store paths. Defaults to `/nix/store`. This is
4040+ useful when you want to build a partition that only contains store
4141+ paths and is mounted under `/nix/store` or if you want to create the
4242+ store paths below a parent path (e.g., `/@nix/nix/store`).
4343+ '';
4444+ };
4545+4646+ contents = lib.mkOption {
4747+ type =
4848+ with lib.types;
4949+ attrsOf (submodule {
5050+ options = {
5151+ source = lib.mkOption {
5252+ type = types.path;
5353+ description = "Path of the source file.";
5454+ };
4455 };
4545- };
4646- });
4747- default = { };
4848- example = lib.literalExpression ''
4949- {
5050- "/EFI/BOOT/BOOTX64.EFI".source =
5151- "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
5656+ });
5757+ default = { };
5858+ example = lib.literalExpression ''
5959+ {
6060+ "/EFI/BOOT/BOOTX64.EFI".source =
6161+ "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
52625353- "/loader/entries/nixos.conf".source = systemdBootEntry;
5454- }
5555- '';
5656- description = "The contents to end up in the filesystem image.";
5757- };
6363+ "/loader/entries/nixos.conf".source = systemdBootEntry;
6464+ }
6565+ '';
6666+ description = "The contents to end up in the filesystem image.";
6767+ };
58685959- repartConfig = lib.mkOption {
6060- type =
6161- with lib.types;
6262- attrsOf (oneOf [
6363- str
6464- int
6565- bool
6666- (listOf str)
6767- ]);
6868- example = {
6969- Type = "home";
7070- SizeMinBytes = "512M";
7171- SizeMaxBytes = "2G";
6969+ repartConfig = lib.mkOption {
7070+ type =
7171+ with lib.types;
7272+ attrsOf (oneOf [
7373+ str
7474+ int
7575+ bool
7676+ (listOf str)
7777+ ]);
7878+ example = {
7979+ Type = "home";
8080+ SizeMinBytes = "512M";
8181+ SizeMaxBytes = "2G";
8282+ };
8383+ description = ''
8484+ Specify the repart options for a partiton as a structural setting.
8585+ See {manpage}`repart.d(5)`
8686+ for all available options.
8787+ '';
7288 };
7373- description = ''
7474- Specify the repart options for a partiton as a structural setting.
7575- See {manpage}`repart.d(5)`
7676- for all available options.
7777- '';
8989+ };
9090+9191+ config = lib.mkIf (config.stripNixStorePrefix == true) {
9292+ nixStorePrefix = "/";
7893 };
7994 };
8080- };
81958296 mkfsOptionsToEnv =
8397 opts:
···350364 }
351365 ) cfg.partitions;
352366353353- warnings = lib.filter (v: v != null) (
367367+ warnings = lib.flatten (
354368 lib.mapAttrsToList (
355369 fileName: partitionConfig:
356370 let
···358372 suggestedMaxLabelLength = GPTMaxLabelLength - 2;
359373 labelLength = builtins.stringLength repartConfig.Label;
360374 in
361361- if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then
362362- ''
363363- The partition label '${repartConfig.Label}'
364364- defined for '${fileName}' is ${toString labelLength} characters long.
365365- The suggested maximum label length is ${toString suggestedMaxLabelLength}.
375375+ lib.optional (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) ''
376376+ The partition label '${repartConfig.Label}'
377377+ defined for '${fileName}' is ${toString labelLength} characters long.
378378+ The suggested maximum label length is ${toString suggestedMaxLabelLength}.
366379367367- If you use sytemd-sysupdate style A/B updates, this might
368368- not leave enough space to increment the version number included in
369369- the label in a future release. For example, if your label is
370370- ${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
371371- you're at version 9, you cannot increment this to 10.
372372- ''
373373- else
374374- null
380380+ If you use sytemd-sysupdate style A/B updates, this might
381381+ not leave enough space to increment the version number included in
382382+ the label in a future release. For example, if your label is
383383+ ${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
384384+ you're at version 9, you cannot increment this to 10.
385385+ ''
386386+ ++ lib.optional (partitionConfig.stripNixStorePrefix != "_mkMergedOptionModule") ''
387387+ The option definition `image.repart.paritions.${fileName}.stripNixStorePrefix`
388388+ has changed to `image.repart.paritions.${fileName}.nixStorePrefix` and now
389389+ accepts the path to use as prefix directly. Use `nixStorePrefix = "/"` to
390390+ achieve the same effect as setting `stripNixStorePrefix = true`.
391391+ ''
375392 ) cfg.partitions
376393 );
377394 };