Unverified Commit 4916e228 authored by Linus Heckemann's avatar Linus Heckemann Committed by GitHub
Browse files

Merge pull request #286176 from nikstur/writable-overlays

nixos/filesystems: init overlayfs
parents 251c69b7 bbce103c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -39,4 +39,5 @@ and non-critical by adding `options = [ "nofail" ];`.
```{=include=} sections
luks-file-systems.section.md
sshfs-file-systems.section.md
overlayfs.section.md
```
+27 −0
Original line number Diff line number Diff line
# Overlayfs {#sec-overlayfs}

NixOS offers a convenient abstraction to create both read-only as well writable
overlays.

```nix
fileSystems = {
  "/writable-overlay" = {
    overlay = {
      lowerdir = [ writableOverlayLowerdir ];
      upperdir = "/.rw-writable-overlay/upper";
      workdir = "/.rw-writable-overlay/work";
    };
    # Mount the writable overlay in the initrd.
    neededForBoot = true;
  };
  "/readonly-overlay".overlay.lowerdir = [
    writableOverlayLowerdir
    writableOverlayLowerdir2
  ];
};
```

If `upperdir` and `workdir` are not null, they will be created before the
overlay is mounted.

To mount an overlay as read-only, you need to provide at least two `lowerdir`s.
+5 −0
Original line number Diff line number Diff line
@@ -272,6 +272,11 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
- The option [`services.nextcloud.config.dbport`] of the Nextcloud module was removed to match upstream.
  The port can be specified in [`services.nextcloud.config.dbhost`](#opt-services.nextcloud.config.dbhost).

- A new abstraction to create both read-only as well as writable overlay file
  systems was added. Available via
  [fileSystems.overlay](#opt-fileSystems._name_.overlay.lowerdir). See also the
  [NixOS docs](#sec-overlayfs).

- `stdenv`: The `--replace` flag in `substitute`, `substituteInPlace`, `substituteAll`, `substituteAllStream`, and `substituteStream` is now deprecated if favor of the new `--replace-fail`, `--replace-warn` and `--replace-quiet`. The deprecated `--replace` equates to `--replace-warn`.

- New options were added to the dnsdist module to enable and configure a DNSCrypt endpoint (see `services.dnsdist.dnscrypt.enable`, etc.).
+1 −0
Original line number Diff line number Diff line
@@ -1527,6 +1527,7 @@
  ./tasks/filesystems/jfs.nix
  ./tasks/filesystems/nfs.nix
  ./tasks/filesystems/ntfs.nix
  ./tasks/filesystems/overlayfs.nix
  ./tasks/filesystems/reiserfs.nix
  ./tasks/filesystems/sshfs.nix
  ./tasks/filesystems/squashfs.nix
+144 −0
Original line number Diff line number Diff line
{ 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 = lib.mdDoc ''
          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 = lib.mdDoc ''
          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 = lib.mdDoc ''
          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);

    };

}
Loading