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