Create an account


Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Fedora - How to Build a Netboot Server, Part 4

#1
How to Build a Netboot Server, Part 4

<div style="margin: 5px 5% 10px 5%;"><img src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/how-to-build-a-netboot-server-part-4.png" width="1024" height="819" title="" alt="" /></div><div><p>One significant limitation of the netboot server built in this series is the operating system image being served is read-only. Some use cases may require the end user to modify the image. For example, an instructor may want to have the students install and configure software packages like MariaDB and Node.js as part of their course walk-through.</p>
<p>An added benefit of writable netboot images is the end user’s “personalized” operating system can follow them to different workstations they may use at later times.</p>
<h2>Change the Bootmenu Application to use HTTPS</h2>
<p>Create a self-signed certificate for the bootmenu application:</p>
<pre>$ sudo -i
# MY_NAME=$(&lt;/etc/hostname)
# MY_TLSD=/opt/bootmenu/tls
# mkdir $MY_TLSD
# openssl req -newkey rsa:2048 -nodes -keyout $MY_TLSD/$MY_NAME.key -x509 -days 3650 -out $MY_TLSD/$MY_NAME.pem</pre>
<p>Verify your certificate’s values. Make sure the “CN” value in the “Subject” line matches the DNS name that your iPXE clients use to connect to your bootmenu server:</p>
<pre># openssl x509 -text -noout -in $MY_TLSD/$MY_NAME.pem</pre>
<p>Next, update the bootmenu application’s <em>listen</em> directive to use the HTTPS port and the newly created certificate and key:</p>
<pre># sed -i "s#listen =&gt; .*#listen =&gt; ['https://$MY_NAME:443?cert=$MY_TLSD/$MY_NAME.pem\&amp;key=$MY_TLSD/$MY_NAME.key\&amp;ciphers=AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA'],#" /opt/bootmenu/bootmenu.conf</pre>
<p>Note the ciphers have been restricted to <a href="http://ipxe.org/crypto">those currently supported by iPXE</a>.</p>
<p>GnuTLS requires the “CAP_DAC_READ_SEARCH” capability, so add it to the bootmenu application’s systemd service:</p>
<pre># sed -i '/^AmbientCapabilities=/ s/$/ CAP_DAC_READ_SEARCH/' /etc/systemd/system/bootmenu.service
# sed -i 's/Serves iPXE Menus over HTTP/Serves iPXE Menus over HTTPS/' /etc/systemd/system/bootmenu.service
# systemctl daemon-reload</pre>
<p>Now, add an exception for the bootmenu service to the firewall and restart the service:</p>
<pre># MY_SUBNET=192.0.2.0
# MY_PREFIX=24
# firewall-cmd --add-rich-rule="rule family='ipv4' source address='$MY_SUBNET/$MY_PREFIX' service name='https' accept"
# firewall-cmd --runtime-to-permanent
# systemctl restart bootmenu.service</pre>
<p>Use <em>wget</em> to verify it’s working:</p>
<pre>$ MY_NAME=server-01.example.edu
$ MY_TLSD=/opt/bootmenu/tls
$ wget -q --ca-certificate=$MY_TLSD/$MY_NAME.pem -O - https://$MY_NAME/menu</pre>
<h2>Add HTTPS to iPXE</h2>
<p>Update <em>init.ipxe</em> to use HTTPS. Then recompile the ipxe bootloader with options to embed and trust the self-signed certificate you created for the bootmenu application:</p>
<pre>$ echo '#define DOWNLOAD_PROTO_HTTPS' &gt;&gt; $HOME/ipxe/src/config/local/general.h
$ sed -i 's/^chain http:/chain https:/' $HOME/ipxe/init.ipxe
$ cp $MY_TLSD/$MY_NAME.pem $HOME/ipxe
$ cd $HOME/ipxe/src
$ make clean
$ make bin-x86_64-efi/ipxe.efi EMBED=../init.ipxe CERT="../$MY_NAME.pem" TRUST="../$MY_NAME.pem"</pre>
<p>You can now copy the HTTPS-enabled iPXE bootloader out to your clients and test that everything is working correctly:</p>
<pre>$ cp $HOME/ipxe/src/bin-x86_64-efi/ipxe.efi $HOME/esp/efi/boot/bootx64.efi</pre>
<h2>Add User Authentication to Mojolicious</h2>
<p>Create a PAM service definition for the bootmenu application:</p>
<pre># dnf install -y pam_krb5
# echo 'auth required pam_krb5.so' &gt; /etc/pam.d/bootmenu</pre>
<p>Add a library to the bootmenu application that uses the Authen-PAM perl module to perform user authentication:</p>
<pre># dnf install -y perl-Authen-PAM;
# MY_MOJO=/opt/bootmenu
# mkdir $MY_MOJO/lib
# cat &lt;&lt; 'END' &gt; $MY_MOJO/lib/PAM.pm
package PAM; use Authen:TongueAM; sub auth { my $success = 0; my $username = shift; my $password = shift; my $callback = sub { my @res; while (@_) { my $code = shift; my $msg = shift; my $ans = ""; $ans = $username if ($code == PAM_PROMPT_ECHO_ON()); $ans = $password if ($code == PAM_PROMPT_ECHO_OFF()); push @res, (PAM_SUCCESS(), $ans); } push @res, PAM_SUCCESS(); return @res; }; my $pamh = new Authen:TongueAM('bootmenu', $username, $callback); { last unless ref $pamh; last unless $pamh-&gt;pam_authenticate() == PAM_SUCCESS; $success = 1; } return $success;
} return 1;
END</pre>
<p>The above code is taken almost verbatim from the Authen:TongueAM::FAQ man page.</p>
<p>Redefine the bootmenu application so it returns a netboot template only if a valid username and password are supplied:</p>
<pre># cat &lt;&lt; 'END' &gt; $MY_MOJO/bootmenu.pl
#!/usr/bin/env perl use lib 'lib'; use PAM;
use Mojolicious::Lite;
use Mojolicious:Tonguelugins;
use Mojo::Util ('url_unescape'); plugin 'Config'; get '/menu';
get '/boot' =&gt; sub { my $c = shift; my $instance = $c-&gt;param('instance'); my $username = $c-&gt;param('username'); my $password = $c-&gt;param('password'); my $template = 'menu'; { last unless $instance =~ /^fc[[:digit:]]{2}$/; last unless $username =~ /^[[:alnum:]]+$/; last unless PAM::auth($username, url_unescape($password)); $template = $instance; } return $c-&gt;render(template =&gt; $template);
}; app-&gt;start;
END</pre>
<p>The bootmenu application now looks for the <em>lib</em> directory relative to its <em>WorkingDirectory</em>. However, by default the working directory is set to the root directory of the server for systemd units. Therefore, you must update the systemd unit to set <em>WorkingDirectory</em> to the root of the bootmenu application instead:</p>
<pre># sed -i "/^RuntimeDirectory=/ a WorkingDirectory=$MY_MOJO" /etc/systemd/system/bootmenu.service
# systemctl daemon-reload</pre>
<p>Update the templates to work with the redefined bootmenu application:</p>
<pre># cd $MY_MOJO/templates
# MY_BOOTMENU_SERVER=$(&lt;/etc/hostname)
# MY_FEDORA_RELEASES="28 29"
# for i in $MY_FEDORA_RELEASES; do echo '#!ipxe' &gt; fc$i.html.ep; grep "^kernel\|initrd" menu.html.ep | grep "fc$i" &gt;&gt; fc$i.html.ep; echo "boot || chain https://$MY_BOOTMENU_SERVER/menu" &gt;&gt; fc$i.html.ep; sed -i "/^:f$i$/,/^boot /c :f$i\nlogin\nchain https://$MY_BOOTMENU_SERVER/boot?instance=fc$i\&amp;username=\${username}\&amp;password=\${password:uristring} || goto failed" menu.html.ep; done</pre>
<p>The result of the last command above should be three files similar to the following:</p>
<p><strong>menu.html.ep</strong>:</p>
<pre>#!ipxe set timeout 5000 :menu
menu iPXE Boot Menu
item --key 1 lcl 1. Microsoft Windows 10
item --key 2 f29 2. RedHat Fedora 29
item --key 3 f28 3. RedHat Fedora 28
choose --timeout ${timeout} --default lcl selected || goto shell
set timeout 0
goto ${selected} :failed
echo boot failed, dropping to shell...
goto shell Confusedhell
echo type 'exit' to get the back to the menu
set timeout 0
shell
goto menu :lcl
exit :f29
login
chain https://server-01.example.edu/boot?insta...p;username=${username}&amp;password=${password:uristring} || goto failed :f28
login
chain https://server-01.example.edu/boot?insta...p;username=${username}&amp;password=${password:uristring} || goto failed</pre>
<p><strong>fc29.html.ep</strong>:</p>
<pre>#!ipxe
kernel --name kernel.efi ${prefix}/vmlinuz-4.19.5-300.fc29.x86_64 initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=192.0.2.91 nameserver=192.0.2.92 root=/dev/disk/by-path/ip-192.0.2.158:3260-iscsi-iqn.edu.example.server-01:fc29-lun-1 netroot=iscsi:192.0.2.158::::iqn.edu.example.server-01:fc29 console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet
initrd --name initrd.img ${prefix}/initramfs-4.19.5-300.fc29.x86_64.img
boot || chain https://server-01.example.edu/menu</pre>
<p><strong>fc28.html.ep</strong>:</p>
<pre>#!ipxe
kernel --name kernel.efi ${prefix}/vmlinuz-4.19.3-200.fc28.x86_64 initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=192.0.2.91 nameserver=192.0.2.92 root=/dev/disk/by-path/ip-192.0.2.158:3260-iscsi-iqn.edu.example.server-01:fc28-lun-1 netroot=iscsi:192.0.2.158::::iqn.edu.example.server-01:fc28 console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet
initrd --name initrd.img ${prefix}/initramfs-4.19.3-200.fc28.x86_64.img
boot || chain https://server-01.example.edu/menu</pre>
<p>Now, restart the bootmenu application and verify authentication is working:</p>
<pre># systemctl restart bootmenu.service</pre>
<h2>Make the iSCSI Target Writeable</h2>
<p>Now that user authentication works through iPXE, you can create per-user, writeable overlays on top of the read-only image on demand when users connect. Using a <a href="https://en.wikipedia.org/wiki/Copy-on-write" target="_blank" rel="noopener">copy-on-write</a> overlay has three advantages over simply copying the original image file for each user:</p>
<ol>
<li>The copy can be created very quickly. This allows creation on-demand.</li>
<li>The copy does not increase the disk usage on the server. Only what the user writes to their personal copy of the image is stored in addition to the original image.</li>
<li>Since most sectors for each copy are the same sectors on the server’s storage, they’ll likely already be loaded in RAM when subsequent users access their copies of the operating system. This improves the server’s performance because RAM is faster than disk I/O.</li>
</ol>
<p>One potential pitfall of using copy-on-write is that once overlays are created, the images on which they are overlayed <em>must not be changed</em>. If they are changed, all the overlays will be corrupted. Then the overlays must be deleted and replaced with new, blank overlays. Even simply mounting the image file in read-write mode can cause sufficient filesystem updates to corrupt the overlays.</p>
<p>Due to the potential for the overlays to be corrupted if the original image is modified, mark the original image as <em>immutable</em> by running:</p>
<pre># chattr +i &lt;/path/to/file&gt;</pre>
<p>You can use <em>lsattr &lt;/path/to/file&gt;</em> to view the status of the immutable flag and use  to <em>chattr -i &lt;/path/to/file&gt;</em> unset the immutable flag. While the immutable flag is set, even the root user or a system process running as root cannot modify or delete the file.</p>
<p>Begin by stopping the <em>tgtd.service</em> so you can change the image files:</p>
<pre># systemctl stop tgtd.service</pre>
<p>It’s normal for this command to take a minute or so to stop when there are connections still open.</p>
<p>Now, remove the read-only iSCSI export. Then update the <em>readonly-root</em> configuration file in the template so the image is no longer read-only:</p>
<pre># MY_FC=fc29
# rm -f /etc/tgt/conf.d/$MY_FC.conf
# TEMP_MNT=$(mktemp -d)
# mount /$MY_FC.img $TEMP_MNT
# sed -i 's/^READONLY=yes$/READONLY=no/' $TEMP_MNT/etc/sysconfig/readonly-root
# sed -i 's/^Storage=volatile$/#Storage=auto/' $TEMP_MNT/etc/systemd/journald.conf
# umount $TEMP_MNT</pre>
<p>Journald was changed from logging to volatile memory back to its default (log to disk if <em>/var/log/journal</em> exists) because a user reported his clients would freeze with an out-of-memory error due to an application generating excessive system logs. The downside to setting logging to disk is that extra write traffic is generated by the clients, and might burden your netboot server with unnecessary I/O. You should decide which option — log to memory or log to disk — is preferable depending on your environment.</p>
<p>Since you won’t make any further changes to the template image, set the immutable flag on it and restart the <em>tgtd.service</em>:</p>
<pre># chattr +i /$MY_FC.img
# systemctl start tgtd.service</pre>
<p>Now, update the bootmenu application:</p>
<pre># cat &lt;&lt; 'END' &gt; $MY_MOJO/bootmenu.pl
#!/usr/bin/env perl use lib 'lib'; use PAM;
use Mojolicious::Lite;
use Mojolicious:Tonguelugins;
use Mojo::Util ('url_unescape'); plugin 'Config'; get '/menu';
get '/boot' =&gt; sub { my $c = shift; my $instance = $c-&gt;param('instance'); my $username = $c-&gt;param('username'); my $password = $c-&gt;param('password'); my $chapscrt; my $template = 'menu'; { last unless $instance =~ /^fc[[:digit:]]{2}$/; last unless $username =~ /^[[:alnum:]]+$/; last unless PAM::auth($username, url_unescape($password)); last unless $chapscrt = `sudo scripts/mktgt $instance $username`; $template = $instance; } return $c-&gt;render(template =&gt; $template, username =&gt; $username, chapscrt =&gt; $chapscrt);
}; app-&gt;start;
END</pre>
<p>This new version of the bootmenu application calls a custom <em>mktgt</em> script which, on success, returns a random <a href="https://en.wikipedia.org/wiki/Challenge-Handshake_Authentication_Protocol" target="_blank" rel="noopener">CHAP</a> password for each new iSCSI target that it creates. The CHAP password prevents one user from mounting another user’s iSCSI target by indirect means. The app only returns the correct iSCSI target password to a user who has successfully authenticated.</p>
<p>The <em>mktgt</em> script is prefixed with <em>sudo</em> because it needs root privileges to create the target.</p>
<p>The <em>$username</em> and <em>$chapscrt</em> variables also pass to the <em>render</em> command so they can be incorporated into the templates returned to the user when necessary.</p>
<p>Next, update our boot templates so they can read the <em>username</em> and <em>chapscrt</em> variables and pass them along to the end user. Also update the templates to mount the root filesystem in <em>rw</em> (read-write) mode:</p>
<pre># cd $MY_MOJO/templates
# sed -i "s/:$MY_FC/:$MY_FC-&lt;%= \$username %&gt;/g" $MY_FC.html.ep
# sed -i "s/ netroot=iscsi:/ netroot=iscsi:&lt;%= \$username %&gt;:&lt;%= \$chapscrt %&gt;@/" $MY_FC.html.ep
# sed -i "s/ ro / rw /" $MY_FC.html.ep</pre>
<p>After running the above commands, you should have boot templates like the following:</p>
<pre>#!ipxe
kernel --name kernel.efi ${prefix}/vmlinuz-4.19.5-300.fc29.x86_64 initrd=initrd.img rw ip=dhcp rd.peerdns=0 nameserver=192.0.2.91 nameserver=192.0.2.92 root=/dev/disk/by-path/ip-192.0.2.158:3260-iscsi-iqn.edu.example.server-01:fc29-&lt;%= $username %&gt;-lun-1 netroot=iscsi:&lt;%= $username %&gt;:&lt;%= $chapscrt %&gt;@192.0.2.158::::iqn.edu.example.server-01:fc29-&lt;%= $username %&gt; console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet
initrd --name initrd.img ${prefix}/initramfs-4.19.5-300.fc29.x86_64.img
boot || chain https://server-01.example.edu/menu</pre>
<p>NOTE: If you need to view the boot template after the variables have been <a href="https://en.wikipedia.org/wiki/String_interpolation" target="_blank" rel="noopener">interpolated</a>, you can insert the “shell” command on its own line just before the “boot” command. Then, when you netboot your client, iPXE gives you an interactive shell where you can enter “imgstat” to view the parameters being passed to the kernel. If everything looks correct, you can type “exit” to leave the shell and continue the boot process.</p>
<p>Now allow the <em>bootmenu</em> user to run the <em>mktgt</em> script (and only that script) as root via <em>sudo</em>:</p>
<pre># echo "bootmenu ALL = NOPASSWD: $MY_MOJO/scripts/mktgt *" &gt; /etc/sudoers.d/bootmenu</pre>
<p>The bootmenu user should not have write access to the <em>mktgt</em> script or any other files under its home directory. All the files under <em>/opt/bootmenu</em> should be owned by root, and should not be writable by any user other than root.</p>
<p>Sudo does not work well with systemd’s <em>DynamicUser</em> option, so create a normal user account and set the systemd service to run as that user:</p>
<pre># useradd -r -c 'iPXE Boot Menu Service' -d /opt/bootmenu -s /sbin/nologin bootmenu
# sed -i 's/^DynamicUser=true$/User=bootmenu/' /etc/systemd/system/bootmenu.service
# systemctl daemon-reload</pre>
<p>Finally, create a directory for the copy-on-write overlays and create the <em>mktgt</em> script that manages the iSCSI targets and their overlayed backing stores:</p>
<pre># mkdir /$MY_FC.cow
# mkdir $MY_MOJO/scripts
# cat &lt;&lt; 'END' &gt; $MY_MOJO/scripts/mktgt
#!/usr/bin/env perl # if another instance of this script is running, wait for it to finish "$ENV{FLOCKER}" eq 'MKTGT' or exec "env FLOCKER=MKTGT flock /tmp $0 @ARGV"; # use "RETURN" to print to STDOUT; everything else goes to STDERR by default
open(RETURN, '&gt;&amp;', STDOUT);
open(STDOUT, '&gt;&amp;', STDERR); my $instance = shift or die "instance not provided";
my $username = shift or die "username not provided"; my $img = "/$instance.img";
my $dir = "/$instance.cow";
my $top = "$dir/$username"; -f "$img" or die "'$img' is not a file"; -d "$dir" or die "'$dir' is not a directory"; my $base;
die unless $base = `losetup --show --read-only --nooverlap --find $img`;
chomp $base; my $size;
die unless $size = `blockdev --getsz $base`;
chomp $size; # create the per-user sparse file if it does not exist
if (! -e "$top") { die unless system("dd if=/dev/zero of=$top status=none bs=512 count=0 seek=$size") == 0;
} # create the copy-on-write overlay if it does not exist
my $cow="$instance-$username";
my $dev="/dev/mapper/$cow";
if (! -e "$dev") { my $over; die unless $over = `losetup --show --nooverlap --find $top`; chomp $over; die unless system("echo 0 $size snapshot $base $over p 8 | dmsetup create $cow") == 0;
} my $tgtadm = '/usr/sbin/tgtadm --lld iscsi'; # get textual representations of the iscsi targets
my $text = `$tgtadm --op show --mode target`;
my @targets = $text =~ /(?:^T.*\n)(?:^ .*\n)*/mg; # convert the textual representations into a hash table
my $targets = {};
foreach (@targets) { my $tgt; my $sid; foreach (split /\n/) { /^Target (\d+)(?{ $tgt = $targets-&gt;{$^N} = [] })/; /I_T nexus: (\d+)(?{ $sid = $^N })/; /Connection: (\d+)(?{ push @{$tgt}, [ $sid, $^N ] })/; }
} my $hostname;
die unless $hostname = `hostname`;
chomp $hostname; my $target = 'iqn.' . join('.', reverse split('\.', $hostname)) . ":$cow"; # find the target id corresponding to the provided target name and
# close any existing connections to it
my $tid = 0;
foreach (@targets) { next unless /^Target (\d+)(?{ $tid = $^N }): $target$/m; foreach (@{$targets-&gt;{$tid}}) { die unless system("$tgtadm --op delete --mode conn --tid $tid --sid $_-&gt;[0] --cid $_-&gt;[1]") == 0; }
} # create a new target if an existing one was not found
if ($tid == 0) { # find an available target id my @ids = (0, sort keys %{$targets}); $tid = 1; while ($ids[$tid]==$tid) { $tid++ } # create the target die unless -e "$dev"; die unless system("$tgtadm --op new --mode target --tid $tid --targetname $target") == 0; die unless system("$tgtadm --op new --mode logicalunit --tid $tid --lun 1 --backing-store $dev") == 0; die unless system("$tgtadm --op bind --mode target --tid $tid --initiator-address ALL") == 0;
} # (re)set the provided target's chap password
my $password = join('', map(chr(int(rand(26))+65), 1..8));
my $accounts = `$tgtadm --op show --mode account`;
if ($accounts =~ / $username$/m) { die unless system("$tgtadm --op delete --mode account --user $username") == 0;
}
die unless system("$tgtadm --op new --mode account --user $username --password $password") == 0;
die unless system("$tgtadm --op bind --mode account --tid $tid --user $username") == 0; # return the new password to the iscsi target on stdout
print RETURN $password;
END
# chmod +x $MY_MOJO/scripts/mktgt</pre>
<p>The above script does five things:</p>
<ol>
<li>It creates the /&lt;instance&gt;.cow/&lt;username&gt; sparse file if it does not already exist.</li>
<li>It creates the /dev/mapper/&lt;instance&gt;-&lt;username&gt; device node that serves as the copy-on-write backing store for the iSCSI target if it does not already exist.</li>
<li>It creates the iqn.&lt;reverse-hostname&gt;:&lt;instance&gt;-&lt;username&gt; iSCSI target if it does not exist. Or, if the target does exist, it closes any existing connections to it because the image can only be opened in read-write mode from one place at a time.</li>
<li>It (re)sets the chap password on the iqn.&lt;reverse-hostname&gt;:&lt;instance&gt;-&lt;username&gt; iSCSI target to a new random value.</li>
<li>It prints the new chap password on <a href="https://en.wikipedia.org/wiki/Standard_streams" target="_blank" rel="noopener">standard output</a> if all of the previous tasks compeleted successfully.</li>
</ol>
<p>You should be able to test the <em>mktgt</em> script from the command line by running it with valid test parameters. For example:</p>
<pre># echo `$MY_MOJO/scripts/mktgt fc29 jsmith`</pre>
<p>When run from the command line, the <em>mktgt</em> script should print out either the eight-character random password for the iSCSI target if it succeeded or the line number on which something went wrong if it failed.</p>
<p>On occasion, you may want to delete an iSCSI target without having to stop the entire service. For example, a user might inadvertently corrupt their personal image, in which case you would need to systematically undo everything that the above <em>mktgt</em> script does so that the next time they log in they will get a copy of the original image.</p>
<p>Below is an <em>rmtgt</em> script that undoes, in reverse order, what the above <em>mktgt</em> script did:</p>
<pre># mkdir $HOME/bin
# cat &lt;&lt; 'END' &gt; $HOME/bin/rmtgt
#!/usr/bin/env perl @ARGV &gt;= 2 or die "usage: $0 &lt;instance&gt; &lt;username&gt; [+d|+f]\n"; my $instance = shift;
my $username = shift; my $rmd = ($ARGV[0] eq '+d'); #remove device node if +d flag is set
my $rmf = ($ARGV[0] eq '+f'); #remove sparse file if +f flag is set
my $cow = "$instance-$username"; my $hostname;
die unless $hostname = `hostname`;
chomp $hostname; my $tgtadm = '/usr/sbin/tgtadm';
my $target = 'iqn.' . join('.', reverse split('\.', $hostname)) . ":$cow"; my $text = `$tgtadm --op show --mode target`;
my @targets = $text =~ /(?:^T.*\n)(?:^ .*\n)*/mg; my $targets = {};
foreach (@targets) { my $tgt; my $sid; foreach (split /\n/) { /^Target (\d+)(?{ $tgt = $targets-&gt;{$^N} = [] })/; /I_T nexus: (\d+)(?{ $sid = $^N })/; /Connection: (\d+)(?{ push @{$tgt}, [ $sid, $^N ] })/; }
} my $tid = 0;
foreach (@targets) { next unless /^Target (\d+)(?{ $tid = $^N }): $target$/m; foreach (@{$targets-&gt;{$tid}}) { die unless system("$tgtadm --op delete --mode conn --tid $tid --sid $_-&gt;[0] --cid $_-&gt;[1]") == 0; } die unless system("$tgtadm --op delete --mode target --tid $tid") == 0; print "target $tid deleted\n"; sleep 1;
} my $dev = "/dev/mapper/$cow";
if ($rmd or ($rmf and -e $dev)) { die unless system("dmsetup remove $cow") == 0; print "device node $dev deleted\n";
} if ($rmf) { my $sf = "/$instance.cow/$username"; die "sparse file $sf not found" unless -e "$sf"; die unless system("rm -f $sf") == 0; die unless not -e "$sf"; print "sparse file $sf deleted\n";
}
END
# chmod +x $HOME/bin/rmtgt</pre>
<p>For example, to use the above script to completely remove the <em>fc29-jsmith</em> target including its backing store device node and its sparse file, run the following:</p>
<pre># rmtgt fc29 jsmith +f</pre>
<p>Once you’ve verified that the <em>mktgt</em> script is working properly, you can restart the bootmenu service. The next time someone netboots, they should receive a personal copy of the the netboot image they can write to:</p>
<pre># systemctl restart bootmenu.service</pre>
<p>Users should now be able to modify the root filesystem as demonstrated in the below screenshot:</p>
<p><img class="alignnone wp-image-24099 size-large" src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/how-to-build-a-netboot-server-part-4.png" alt="" width="616" height="493" /></p>
</div>
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

Forum software by © MyBB Theme © iAndrew 2016