summary refs log tree commit diff
path: root/nixos/modules/services/networking/iscsi/root-initiator.nix
blob: c12aca1bc24d78fed4eff279f919d285e8e198e1 (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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
{ config, lib, pkgs, ... }: with lib;
let
  cfg = config.boot.iscsi-initiator;
in
{
  # If you're booting entirely off another machine you may want to add
  # this snippet to always boot the latest "system" version. It is not
  # enabled by default in case you have an initrd on a local disk:
  #
  #     boot.initrd.postMountCommands = ''
  #       ln -sfn /nix/var/nix/profiles/system/init /mnt-root/init
  #       stage2Init=/init
  #     '';
  #
  # Note: Theoretically you might want to connect to multiple portals and
  # log in to multiple targets, however the authors of this module so far
  # don't have the need or expertise to reasonably implement it. Also,
  # consider carefully before making your boot chain depend on multiple
  # machines to be up.
  options.boot.iscsi-initiator = with types; {
    name = mkOption {
      description = ''
        Name of the iSCSI initiator to boot from. Note, booting from iscsi
        requires networkd based networking.
      '';
      default = null;
      example = "iqn.2020-08.org.linux-iscsi.initiatorhost:example";
      type = nullOr str;
    };

    discoverPortal = mkOption {
      description = ''
        iSCSI portal to boot from.
      '';
      default = null;
      example = "192.168.1.1:3260";
      type = nullOr str;
    };

    target = mkOption {
      description = ''
        Name of the iSCSI target to boot from.
      '';
      default = null;
      example = "iqn.2020-08.org.linux-iscsi.targethost:example";
      type = nullOr str;
    };

    logLevel = mkOption {
      description = ''
        Higher numbers elicits more logs.
      '';
      default = 1;
      example = 8;
      type = int;
    };

    loginAll = mkOption {
      description = ''
        Do not log into a specific target on the portal, but to all that we discover.
        This overrides setting target.
      '';
      type = bool;
      default = false;
    };

    extraIscsiCommands = mkOption {
      description = "Extra iscsi commands to run in the initrd.";
      default = "";
      type = lines;
    };

    extraConfig = mkOption {
      description = "Extra lines to append to /etc/iscsid.conf";
      default = null;
      type = nullOr lines;
    };

    extraConfigFile = mkOption {
      description = ''
        Append an additional file's contents to `/etc/iscsid.conf`. Use a non-store path
        and store passwords in this file. Note: the file specified here must be available
        in the initrd, see: `boot.initrd.secrets`.
      '';
      default = null;
      type = nullOr str;
    };
  };

  config = mkIf (cfg.name != null) {
    # The "scripted" networking configuration (ie: non-networkd)
    # doesn't properly order the start and stop of the interfaces, and the
    # network interfaces are torn down before unmounting disks. Since this
    # module is specifically for very-early-boot network mounts, we need
    # the network to stay on.
    #
    # We could probably fix the scripted options to properly order, but I'm
    # not inclined to invest that time today. Hopefully this gets users far
    # enough along and they can just use networkd.
    networking.useNetworkd = true;
    networking.useDHCP = false; # Required to set useNetworkd = true

    boot.initrd = {
      network.enable = true;

      # By default, the stage-1 disables the network and resets the interfaces
      # on startup. Since our startup disks are on the network, we can't let
      # the network not work.
      network.flushBeforeStage2 = false;

      kernelModules = [ "iscsi_tcp" ];

      extraUtilsCommands = ''
        copy_bin_and_libs ${pkgs.openiscsi}/bin/iscsid
        copy_bin_and_libs ${pkgs.openiscsi}/bin/iscsiadm
        ${optionalString (!config.boot.initrd.network.ssh.enable) "cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib"}

        mkdir -p $out/etc/iscsi
        cp ${config.environment.etc.hosts.source} $out/etc/hosts
        cp ${pkgs.openiscsi}/etc/iscsi/iscsid.conf $out/etc/iscsi/iscsid.fragment.conf
        chmod +w $out/etc/iscsi/iscsid.fragment.conf
        cat << 'EOF' >> $out/etc/iscsi/iscsid.fragment.conf
        ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
        EOF
      '';

      extraUtilsCommandsTest = ''
        $out/bin/iscsiadm --version
      '';

      preLVMCommands = let
        extraCfgDumper = optionalString (cfg.extraConfigFile != null) ''
          if [ -f "${cfg.extraConfigFile}" ]; then
            printf "\n# The following is from ${cfg.extraConfigFile}:\n"
            cat "${cfg.extraConfigFile}"
          else
            echo "Warning: boot.iscsi-initiator.extraConfigFile ${cfg.extraConfigFile} does not exist!" >&2
          fi
        '';
      in ''
        ${optionalString (!config.boot.initrd.network.ssh.enable) ''
        # stolen from initrd-ssh.nix
        echo 'root:x:0:0:root:/root:/bin/ash' > /etc/passwd
        echo 'passwd: files' > /etc/nsswitch.conf
      ''}

        cp -f $extraUtils/etc/hosts /etc/hosts

        mkdir -p /etc/iscsi /run/lock/iscsi
        echo "InitiatorName=${cfg.name}" > /etc/iscsi/initiatorname.iscsi

        (
          cat "$extraUtils/etc/iscsi/iscsid.fragment.conf"
          printf "\n"
          ${optionalString cfg.loginAll ''echo "node.startup = automatic"''}
          ${extraCfgDumper}
        ) > /etc/iscsi/iscsid.conf

        iscsid --foreground --no-pid-file --debug ${toString cfg.logLevel} &
        iscsiadm --mode discoverydb \
          --type sendtargets \
          --discover \
          --portal ${escapeShellArg cfg.discoverPortal} \
          --debug ${toString cfg.logLevel}

        ${if cfg.loginAll then ''
        iscsiadm --mode node --loginall all
      '' else ''
        iscsiadm --mode node --targetname ${escapeShellArg cfg.target} --login
      ''}

        ${cfg.extraIscsiCommands}

        pkill -9 iscsid
      '';
    };

    services.openiscsi = {
      enable = true;
      inherit (cfg) name;
    };

    assertions = [
      {
        assertion = cfg.loginAll -> cfg.target == null;
        message = "iSCSI target name is set while login on all portals is enabled.";
      }
    ];
  };
}