nixos/image/repart: allow replacing `/nix/store` (#443200)

authored by nikstur and committed by GitHub 31da6736 46103774

+147 -80
+46 -4
nixos/doc/manual/installation/building-images-via-systemd-repart.chapter.md
··· 40 40 } 41 41 ``` 42 42 43 - ## Nix Store Partition {#sec-image-repart-store-partition} 43 + ## 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} 44 50 45 51 You can define a partition that only contains the Nix store and then mount it 46 52 under `/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. 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. 49 55 50 56 ```nix 51 57 { ··· 54 60 image.repart.partitions = { 55 61 "store" = { 56 62 storePaths = [ config.system.build.toplevel ]; 57 - stripNixStorePrefix = true; 63 + nixStorePrefix = "/"; 58 64 repartConfig = { 59 65 Type = "linux-generic"; 60 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"; 61 103 # ... 62 104 }; 63 105 };
+6
nixos/doc/manual/redirects.json
··· 302 302 "sec-image-repart": [ 303 303 "index.html#sec-image-repart" 304 304 ], 305 + "sec-image-repart-store-paths": [ 306 + "index.html#sec-image-repart-store-paths" 307 + ], 305 308 "sec-image-repart-store-partition": [ 306 309 "index.html#sec-image-repart-store-partition" 310 + ], 311 + "sec-image-repart-store-subvolume": [ 312 + "index.html#sec-image-repart-store-subvolume" 307 313 ], 308 314 "sec-image-repart-appliance": [ 309 315 "index.html#sec-image-repart-appliance"
+9 -7
nixos/modules/image/amend-repart-definitions.py
··· 36 36 37 37 38 38 def add_closure_to_definition( 39 - definition: Path, closure: Path | None, strip_nix_store_prefix: bool | None 39 + definition: Path, closure: Path | None, nix_store_prefix: str | None 40 40 ) -> None: 41 41 """Add CopyFiles= instructions to a definition for all paths in the closure. 42 42 43 - If strip_nix_store_prefix is True, `/nix/store` is stripped from the target path. 43 + Replace `/nix/store` with the value of nix_store_prefix. 44 44 """ 45 45 if not closure: 46 46 return ··· 52 52 continue 53 53 54 54 source = Path(line.strip()) 55 - target = str(source.relative_to("/nix/store/")) 56 - target = f":/{target}" if strip_nix_store_prefix else "" 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}" 57 59 58 - copy_files_lines.append(f"CopyFiles={source}{target}\n") 60 + copy_files_lines.append(f"{option}\n") 59 61 60 62 with open(definition, "a") as f: 61 63 f.writelines(copy_files_lines) ··· 102 104 add_contents_to_definition(definition, contents) 103 105 104 106 closure = config.get("closure") 105 - strip_nix_store_prefix = config.get("stripNixStorePrefix") 106 - add_closure_to_definition(definition, closure, strip_nix_store_prefix) 107 + nix_store_prefix = config.get("nixStorePrefix") 108 + add_closure_to_definition(definition, closure, nix_store_prefix) 107 109 108 110 print(target_dir.absolute()) 109 111
+86 -69
nixos/modules/image/repart.nix
··· 15 15 16 16 inherit (utils.systemdUtils.lib) GPTMaxLabelLength; 17 17 18 - 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 - }; 18 + 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 + }; 25 27 26 - 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 - }; 28 + # Superseded by `nixStorePrefix`. Unfortunately, `mkChangedOptionModule` 29 + # does not support submodules. 30 + stripNixStorePrefix = lib.mkOption { 31 + default = "_mkMergedOptionModule"; 32 + visible = false; 33 + }; 35 34 36 - 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."; 35 + 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 + }; 44 55 }; 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"; 56 + }); 57 + default = { }; 58 + example = lib.literalExpression '' 59 + { 60 + "/EFI/BOOT/BOOTX64.EFI".source = 61 + "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi"; 52 62 53 - "/loader/entries/nixos.conf".source = systemdBootEntry; 54 - } 55 - ''; 56 - description = "The contents to end up in the filesystem image."; 57 - }; 63 + "/loader/entries/nixos.conf".source = systemdBootEntry; 64 + } 65 + ''; 66 + description = "The contents to end up in the filesystem image."; 67 + }; 58 68 59 - 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"; 69 + 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 + ''; 72 88 }; 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 - ''; 89 + }; 90 + 91 + config = lib.mkIf (config.stripNixStorePrefix == true) { 92 + nixStorePrefix = "/"; 78 93 }; 79 94 }; 80 - }; 81 95 82 96 mkfsOptionsToEnv = 83 97 opts: ··· 350 364 } 351 365 ) cfg.partitions; 352 366 353 - warnings = lib.filter (v: v != null) ( 367 + warnings = lib.flatten ( 354 368 lib.mapAttrsToList ( 355 369 fileName: partitionConfig: 356 370 let ··· 358 372 suggestedMaxLabelLength = GPTMaxLabelLength - 2; 359 373 labelLength = builtins.stringLength repartConfig.Label; 360 374 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}. 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}. 366 379 367 - 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 380 + 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 + '' 375 392 ) cfg.partitions 376 393 ); 377 394 };