1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7with lib;
8let
9 cfg = config.boot.iscsi-initiator;
10in
11{
12 # If you're booting entirely off another machine you may want to add
13 # this snippet to always boot the latest "system" version. It is not
14 # enabled by default in case you have an initrd on a local disk:
15 #
16 # boot.initrd.postMountCommands = ''
17 # ln -sfn /nix/var/nix/profiles/system/init /mnt-root/init
18 # stage2Init=/init
19 # '';
20 #
21 # Note: Theoretically you might want to connect to multiple portals and
22 # log in to multiple targets, however the authors of this module so far
23 # don't have the need or expertise to reasonably implement it. Also,
24 # consider carefully before making your boot chain depend on multiple
25 # machines to be up.
26 options.boot.iscsi-initiator = with types; {
27 name = mkOption {
28 description = ''
29 Name of the iSCSI initiator to boot from. Note, booting from iscsi
30 requires networkd based networking.
31 '';
32 default = null;
33 example = "iqn.2020-08.org.linux-iscsi.initiatorhost:example";
34 type = nullOr str;
35 };
36
37 discoverPortal = mkOption {
38 description = ''
39 iSCSI portal to boot from.
40 '';
41 default = null;
42 example = "192.168.1.1:3260";
43 type = nullOr str;
44 };
45
46 target = mkOption {
47 description = ''
48 Name of the iSCSI target to boot from.
49 '';
50 default = null;
51 example = "iqn.2020-08.org.linux-iscsi.targethost:example";
52 type = nullOr str;
53 };
54
55 logLevel = mkOption {
56 description = ''
57 Higher numbers elicits more logs.
58 '';
59 default = 1;
60 example = 8;
61 type = int;
62 };
63
64 loginAll = mkOption {
65 description = ''
66 Do not log into a specific target on the portal, but to all that we discover.
67 This overrides setting target.
68 '';
69 type = bool;
70 default = false;
71 };
72
73 extraIscsiCommands = mkOption {
74 description = "Extra iscsi commands to run in the initrd.";
75 default = "";
76 type = lines;
77 };
78
79 extraConfig = mkOption {
80 description = "Extra lines to append to /etc/iscsid.conf";
81 default = null;
82 type = nullOr lines;
83 };
84
85 extraConfigFile = mkOption {
86 description = ''
87 Append an additional file's contents to `/etc/iscsid.conf`. Use a non-store path
88 and store passwords in this file. Note: the file specified here must be available
89 in the initrd, see: `boot.initrd.secrets`.
90 '';
91 default = null;
92 type = nullOr str;
93 };
94 };
95
96 config = mkIf (cfg.name != null) {
97 # The "scripted" networking configuration (ie: non-networkd)
98 # doesn't properly order the start and stop of the interfaces, and the
99 # network interfaces are torn down before unmounting disks. Since this
100 # module is specifically for very-early-boot network mounts, we need
101 # the network to stay on.
102 #
103 # We could probably fix the scripted options to properly order, but I'm
104 # not inclined to invest that time today. Hopefully this gets users far
105 # enough along and they can just use networkd.
106 networking.useNetworkd = true;
107 networking.useDHCP = false; # Required to set useNetworkd = true
108
109 boot.initrd = {
110 network.enable = true;
111
112 # By default, the stage-1 disables the network and resets the interfaces
113 # on startup. Since our startup disks are on the network, we can't let
114 # the network not work.
115 network.flushBeforeStage2 = false;
116
117 kernelModules = [ "iscsi_tcp" ];
118
119 extraUtilsCommands = ''
120 copy_bin_and_libs ${pkgs.openiscsi}/bin/iscsid
121 copy_bin_and_libs ${pkgs.openiscsi}/bin/iscsiadm
122 ${optionalString (
123 !config.boot.initrd.network.ssh.enable
124 ) "cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib"}
125
126 mkdir -p $out/etc/iscsi
127 cp ${config.environment.etc.hosts.source} $out/etc/hosts
128 cp ${pkgs.openiscsi}/etc/iscsi/iscsid.conf $out/etc/iscsi/iscsid.fragment.conf
129 chmod +w $out/etc/iscsi/iscsid.fragment.conf
130 cat << 'EOF' >> $out/etc/iscsi/iscsid.fragment.conf
131 ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
132 EOF
133 '';
134
135 extraUtilsCommandsTest = ''
136 $out/bin/iscsiadm --version
137 '';
138
139 preLVMCommands =
140 let
141 extraCfgDumper = optionalString (cfg.extraConfigFile != null) ''
142 if [ -f "${cfg.extraConfigFile}" ]; then
143 printf "\n# The following is from ${cfg.extraConfigFile}:\n"
144 cat "${cfg.extraConfigFile}"
145 else
146 echo "Warning: boot.iscsi-initiator.extraConfigFile ${cfg.extraConfigFile} does not exist!" >&2
147 fi
148 '';
149 in
150 ''
151 ${optionalString (!config.boot.initrd.network.ssh.enable) ''
152 # stolen from initrd-ssh.nix
153 echo 'root:x:0:0:root:/root:/bin/ash' > /etc/passwd
154 echo 'passwd: files' > /etc/nsswitch.conf
155 ''}
156
157 cp -f $extraUtils/etc/hosts /etc/hosts
158
159 mkdir -p /etc/iscsi /run/lock/iscsi
160 echo "InitiatorName=${cfg.name}" > /etc/iscsi/initiatorname.iscsi
161
162 (
163 cat "$extraUtils/etc/iscsi/iscsid.fragment.conf"
164 printf "\n"
165 ${optionalString cfg.loginAll ''echo "node.startup = automatic"''}
166 ${extraCfgDumper}
167 ) > /etc/iscsi/iscsid.conf
168
169 iscsid --foreground --no-pid-file --debug ${toString cfg.logLevel} &
170 iscsiadm --mode discoverydb \
171 --type sendtargets \
172 --discover \
173 --portal ${escapeShellArg cfg.discoverPortal} \
174 --debug ${toString cfg.logLevel}
175
176 ${
177 if cfg.loginAll then
178 ''
179 iscsiadm --mode node --loginall all
180 ''
181 else
182 ''
183 iscsiadm --mode node --targetname ${escapeShellArg cfg.target} --login
184 ''
185 }
186
187 ${cfg.extraIscsiCommands}
188
189 pkill -9 iscsid
190 '';
191 };
192
193 services.openiscsi = {
194 enable = true;
195 inherit (cfg) name;
196 };
197
198 assertions = [
199 {
200 assertion = cfg.loginAll -> cfg.target == null;
201 message = "iSCSI target name is set while login on all portals is enabled.";
202 }
203 {
204 assertion = !config.boot.initrd.systemd.enable;
205 message = "systemd stage 1 does not support iscsi yet.";
206 }
207 ];
208 };
209}