about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/tasks/filesystems/overlayfs.nix
blob: 2d876c92a1fd803fd4d6bccf867f801246c83888 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
{ config, lib, pkgs, utils, ... }:

let
  # The scripted initrd contains some magic to add the prefix to the
  # paths just in time, so we don't add it here.
  sysrootPrefix = fs:
    if config.boot.initrd.systemd.enable && (utils.fsNeededForBoot fs) then
      "/sysroot"
    else
      "";

  # Returns a service that creates the required directories before the mount is
  # created.
  preMountService = _name: fs:
    let
      prefix = sysrootPrefix fs;

      escapedMountpoint = utils.escapeSystemdPath (prefix + fs.mountPoint);
      mountUnit = "${escapedMountpoint}.mount";

      upperdir = prefix + fs.overlay.upperdir;
      workdir = prefix + fs.overlay.workdir;
    in
    lib.mkIf (fs.overlay.upperdir != null)
      {
        "rw-${escapedMountpoint}" = {
          requiredBy = [ mountUnit ];
          before = [ mountUnit ];
          unitConfig = {
            DefaultDependencies = false;
            RequiresMountsFor = "${upperdir} ${workdir}";
          };
          serviceConfig = {
            Type = "oneshot";
            ExecStart = "${pkgs.coreutils}/bin/mkdir -p -m 0755 ${upperdir} ${workdir}";
          };
        };
      };

  overlayOpts = { config, ... }: {

    options.overlay = {

      lowerdir = lib.mkOption {
        type = with lib.types; nullOr (nonEmptyListOf (either str pathInStore));
        default = null;
        description = ''
          The list of path(s) to the lowerdir(s).

          To create a writable overlay, you MUST provide an upperdir and a
          workdir.

          You can create a read-only overlay when you provide multiple (at
          least 2!) lowerdirs and neither an upperdir nor a workdir.
        '';
      };

      upperdir = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = ''
          The path to the upperdir.

          If this is null, a read-only overlay is created using the lowerdir.

          If you set this to some value you MUST also set `workdir`.
        '';
      };

      workdir = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = ''
          The path to the workdir.

          This MUST be set if you set `upperdir`.
        '';
      };

    };

    config = lib.mkIf (config.overlay.lowerdir != null) {
      fsType = "overlay";
      device = lib.mkDefault "overlay";

      options =
        let
          prefix = sysrootPrefix config;

          lowerdir = map (s: prefix + s) config.overlay.lowerdir;
          upperdir = prefix + config.overlay.upperdir;
          workdir = prefix + config.overlay.workdir;
        in
        [
          "lowerdir=${lib.concatStringsSep ":" lowerdir}"
        ] ++ lib.optionals (config.overlay.upperdir != null) [
          "upperdir=${upperdir}"
          "workdir=${workdir}"
        ] ++ (map (s: "x-systemd.requires-mounts-for=${s}") lowerdir);
    };

  };
in

{

  options = {

    # Merge the overlay options into the fileSystems option.
    fileSystems = lib.mkOption {
      type = lib.types.attrsOf (lib.types.submodule [ overlayOpts ]);
    };

  };

  config =
    let
      overlayFileSystems = lib.filterAttrs (_name: fs: (fs.overlay.lowerdir != null)) config.fileSystems;
      initrdFileSystems = lib.filterAttrs (_name: utils.fsNeededForBoot) overlayFileSystems;
      userspaceFileSystems = lib.filterAttrs (_name: fs: (!utils.fsNeededForBoot fs)) overlayFileSystems;
    in
    {

      boot.initrd.availableKernelModules = lib.mkIf (initrdFileSystems != { }) [ "overlay" ];

      assertions = lib.concatLists (lib.mapAttrsToList
        (_name: fs: [
          {
            assertion = (fs.overlay.upperdir == null) == (fs.overlay.workdir == null);
            message = "You cannot define a `lowerdir` without a `workdir` and vice versa for mount point: ${fs.mountPoint}";
          }
          {
            assertion = (fs.overlay.lowerdir != null && fs.overlay.upperdir == null) -> (lib.length fs.overlay.lowerdir) >= 2;
            message = "A read-only overlay (without an `upperdir`) requires at least 2 `lowerdir`s: ${fs.mountPoint}";
          }
        ])
        config.fileSystems);

      boot.initrd.systemd.services = lib.mkMerge (lib.mapAttrsToList preMountService initrdFileSystems);
      systemd.services = lib.mkMerge (lib.mapAttrsToList preMountService userspaceFileSystems);

    };

}