nixos/image/repart: allow replacing /nix/store

This commit is contained in:
Vincent Haupert
2025-09-14 22:24:12 +02:00
parent 9212727496
commit 973fbf96a4
4 changed files with 152 additions and 85 deletions

View File

@@ -40,12 +40,18 @@ An example of how to build an image:
}
```
## Nix Store Partition {#sec-image-repart-store-partition}
## Nix Store Paths {#sec-image-repart-store-paths}
If you want to rewrite Nix store paths, e.g., to remove the `/nix/store` prefix
or to nest it below a parent path, you can do that through the
`nixStorePrefix` option.
### Nix Store Partition {#sec-image-repart-store-partition}
You can define a partition that only contains the Nix store and then mount it
under `/nix/store`. Because the `/nix/store` part of the paths is already
determined by the mount point, you have to set `stripNixStorePrefix = true;` so
that the prefix is stripped from the paths before copying them into the image.
determined by the mount point, you have to set `nixStorePrefix = "/"` so
that `/nix/store` is stripped from the paths before copying them into the image.
```nix
{
@@ -54,7 +60,7 @@ that the prefix is stripped from the paths before copying them into the image.
image.repart.partitions = {
"store" = {
storePaths = [ config.system.build.toplevel ];
stripNixStorePrefix = true;
nixStorePrefix = "/";
repartConfig = {
Type = "linux-generic";
Label = "nix-store";
@@ -65,6 +71,42 @@ that the prefix is stripped from the paths before copying them into the image.
}
```
### Nix Store Subvolume {#sec-image-repart-store-subvolume}
Alternatively, you can create a Btrfs subvolume `/@nix-store` containing the
Nix store and mount it on `/nix/store`:
```nix
{
fileSystems."/" = {
device = "/dev/disk/by-partlabel/root";
fsType = "btrfs";
options = [ "subvol=/@" ];
};
fileSystems."/nix/store" = {
device = "/dev/disk/by-partlabel/root";
fsType = "btrfs";
options = [ "subvol=/@nix-store" ];
};
image.repart.partitions = {
"root" = {
storePaths = [ config.system.build.toplevel ];
nixStorePrefix = "/@nix-store";
repartConfig = {
Type = "root";
Label = "root";
Format = "btrfs";
Subvolumes = "/@ /@nix-store";
MakeDirectories = "/@ /@nix-store";
# ...
};
};
};
}
```
## Appliance Image {#sec-image-repart-appliance}
The `image/repart.nix` module can also be used to build self-contained [software

View File

@@ -287,9 +287,15 @@
"sec-image-repart": [
"index.html#sec-image-repart"
],
"sec-image-repart-store-paths": [
"index.html#sec-image-repart-store-paths"
],
"sec-image-repart-store-partition": [
"index.html#sec-image-repart-store-partition"
],
"sec-image-repart-store-subvolume": [
"index.html#sec-image-repart-store-subvolume"
],
"sec-image-repart-appliance": [
"index.html#sec-image-repart-appliance"
],

View File

@@ -36,11 +36,11 @@ def add_contents_to_definition(
def add_closure_to_definition(
definition: Path, closure: Path | None, strip_nix_store_prefix: bool | None
definition: Path, closure: Path | None, nix_store_prefix: str | None
) -> None:
"""Add CopyFiles= instructions to a definition for all paths in the closure.
If strip_nix_store_prefix is True, `/nix/store` is stripped from the target path.
Replace `/nix/store` with the value of nix_store_prefix.
"""
if not closure:
return
@@ -52,10 +52,12 @@ def add_closure_to_definition(
continue
source = Path(line.strip())
target = str(source.relative_to("/nix/store/"))
target = f":/{target}" if strip_nix_store_prefix else ""
option = f"CopyFiles={source}"
if nix_store_prefix:
target = nix_store_prefix / source.relative_to("/nix/store/")
option = f"{option}:{target}"
copy_files_lines.append(f"CopyFiles={source}{target}\n")
copy_files_lines.append(f"{option}\n")
with open(definition, "a") as f:
f.writelines(copy_files_lines)
@@ -102,8 +104,8 @@ def main() -> None:
add_contents_to_definition(definition, contents)
closure = config.get("closure")
strip_nix_store_prefix = config.get("stripNixStorePrefix")
add_closure_to_definition(definition, closure, strip_nix_store_prefix)
nix_store_prefix = config.get("nixStorePrefix")
add_closure_to_definition(definition, closure, nix_store_prefix)
print(target_dir.absolute())

View File

@@ -15,7 +15,9 @@ let
inherit (utils.systemdUtils.lib) GPTMaxLabelLength;
partitionOptions = {
partitionOptions =
{ config, ... }:
{
options = {
storePaths = lib.mkOption {
type = with lib.types; listOf path;
@@ -23,13 +25,21 @@ let
description = "The store paths to include in the partition.";
};
# Superseded by `nixStorePrefix`. Unfortunately, `mkChangedOptionModule`
# does not support submodules.
stripNixStorePrefix = lib.mkOption {
type = lib.types.bool;
default = false;
default = "_mkMergedOptionModule";
visible = false;
};
nixStorePrefix = lib.mkOption {
type = lib.types.path;
default = "/nix/store";
description = ''
Whether to strip `/nix/store/` from the store paths. This is useful
when you want to build a partition that only contains store paths and
is mounted under `/nix/store`.
The prefix to use for store paths. Defaults to `/nix/store`. This is
useful when you want to build a partition that only contains store
paths and is mounted under `/nix/store` or if you want to create the
store paths below a parent path (e.g., `/@nix/nix/store`).
'';
};
@@ -77,6 +87,10 @@ let
'';
};
};
config = lib.mkIf (config.stripNixStorePrefix == true) {
nixStorePrefix = "/";
};
};
mkfsOptionsToEnv =
@@ -350,7 +364,7 @@ in
}
) cfg.partitions;
warnings = lib.filter (v: v != null) (
warnings = lib.flatten (
lib.mapAttrsToList (
fileName: partitionConfig:
let
@@ -358,8 +372,7 @@ in
suggestedMaxLabelLength = GPTMaxLabelLength - 2;
labelLength = builtins.stringLength repartConfig.Label;
in
if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then
''
lib.optional (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) ''
The partition label '${repartConfig.Label}'
defined for '${fileName}' is ${toString labelLength} characters long.
The suggested maximum label length is ${toString suggestedMaxLabelLength}.
@@ -370,8 +383,12 @@ in
${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
you're at version 9, you cannot increment this to 10.
''
else
null
++ lib.optional (partitionConfig.stripNixStorePrefix != "_mkMergedOptionModule") ''
The option definition `image.repart.paritions.${fileName}.stripNixStorePrefix`
has changed to `image.repart.paritions.${fileName}.nixStorePrefix` and now
accepts the path to use as prefix directly. Use `nixStorePrefix = "/"` to
achieve the same effect as setting `stripNixStorePrefix = true`.
''
) cfg.partitions
);
};