歡迎光臨
每天分享高質量文章

如何構建一臺網絡引導服務器(四) | Linux 中國

在本系列教程中所構建的網絡引導服務器有一個很重要的限制,那就是所提供的操作系統鏡像是只讀的。一些使用場景或許要求終端用戶能夠修改操作系統鏡像。
— Gregory Bartholomew


致謝
譯自 | fedoramagazine.org 
作者 | Gregory Bartholomew
譯者 | LCTT / qhwdw

在本系列教程中所構建的網絡引導服務器有一個很重要的限制,那就是所提供的操作系統鏡像是只讀的。一些使用場景或許要求終端用戶能夠修改操作系統鏡像。例如,一些教師或許希望學生能夠安裝和配置一些像 MariaDB 和 Node.js 這樣的包來做為他們課程練習的一部分。

可寫鏡像的另外的好處是,終端用戶“私人定製”的操作系統,在下次不同的工作站上使用時能夠“跟著”他們。

修改 Bootmenu 應用程式以使用 HTTPS

為 bootmenu 應用程式創建一個自簽名的證書:

  1. $ sudo -i
  2. # MY_NAME=$(</etc/hostname)
  3. # MY_TLSD=/opt/bootmenu/tls
  4. # mkdir $MY_TLSD
  5. # openssl req -newkey rsa:2048 -nodes -keyout $MY_TLSD/$MY_NAME.key -x509 -days 3650 -out $MY_TLSD/$MY_NAME.pem

驗證你的證書的值。確保 Subject 行中 CN 的值與你的 iPXE 客戶端連接你的網絡引導服務器所使用的 DNS 名字是相匹配的:

  1. # openssl x509 -text -noout -in $MY_TLSD/$MY_NAME.pem

接下來,更新 bootmenu 應用程式去監聽 HTTPS 端口和新創建的證書及密鑰:

  1. # sed -i "s#listen => .*#listen => ['https://$MY_NAME:443?cert=$MY_TLSD/$MY_NAME.pem\&key;=$MY_TLSD/$MY_NAME.key\&ciphers;=AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA'],#" /opt/bootmenu/bootmenu.conf

註意 iPXE 當前支持的[1] 加密演算法是有限制的。

GnuTLS 要求 “CAPDACREAD_SEARCH” 能力,因此將它添加到 bootmenu 應用程式的 systemd 服務:

  1. # sed -i '/^AmbientCapabilities=/ s/$/ CAP_DAC_READ_SEARCH/' /etc/systemd/system/bootmenu.service
  2. # sed -i 's/Serves iPXE Menus over HTTP/Serves iPXE Menus over HTTPS/' /etc/systemd/system/bootmenu.service
  3. # systemctl daemon-reload

現在,在防火牆中為 bootmenu 服務添加一個例外規則並重啟動該服務:

  1. # MY_SUBNET=192.0.2.0
  2. # MY_PREFIX=24
  3. # firewall-cmd --add-rich-rule="rule family='ipv4' source address='$MY_SUBNET/$MY_PREFIX' service name='https' accept"
  4. # firewall-cmd --runtime-to-permanent
  5. # systemctl restart bootmenu.service

使用 wget 去驗證是否工作正常:

  1. $ MY_NAME=server-01.example.edu
  2. $ MY_TLSD=/opt/bootmenu/tls
  3. $ wget -q --ca-certificate=$MY_TLSD/$MY_NAME.pem -O - https://$MY_NAME/menu

添加 HTTPS 到 iPXE

更新 init.ipxe 去使用 HTTPS。接著使用選項重新編譯 ipxe 引導加載器,以便它包含和信任你為 bootmenu 應用程式創建的自簽名證書:

  1. $ echo '#define DOWNLOAD_PROTO_HTTPS' >> $HOME/ipxe/src/config/local/general.h
  2. $ sed -i 's/^chain http:/chain https:/' $HOME/ipxe/init.ipxe
  3. $ cp $MY_TLSD/$MY_NAME.pem $HOME/ipxe
  4. $ cd $HOME/ipxe/src
  5. $ make clean
  6. $ make bin-x86_64-efi/ipxe.efi EMBED=../init.ipxe CERT="../$MY_NAME.pem" TRUST="../$MY_NAME.pem"

你現在可以將啟用了 HTTPS 的 iPXE 引導加載器複製到你的客戶端上,並測試它能否正常工作:

  1. $ cp $HOME/ipxe/src/bin-x86_64-efi/ipxe.efi $HOME/esp/efi/boot/bootx64.efi

添加用戶驗證到 Mojolicious 中

為 bootmenu 應用程式創建一個 PAM 服務定義:

  1. # dnf install -y pam_krb5
  2. # echo 'auth required pam_krb5.so' > /etc/pam.d/bootmenu

添加一個庫到 bootmenu 應用程式中,它使用 Authen-PAM 的 Perl 模塊去執行用戶驗證:

  1. # dnf install -y perl-Authen-PAM;
  2. # MY_MOJO=/opt/bootmenu
  3. # mkdir $MY_MOJO/lib
  4. # cat << 'END' > $MY_MOJO/lib/PAM.pm
  5. package PAM;

  6. use Authen::PAM;

  7. sub auth {
  8.   my $success = 0;

  9.   my $username = shift;
  10.   my $password = shift;

  11.   my $callback = sub {
  12.      my @res;
  13.      while (@_) {
  14.         my $code = shift;
  15.         my $msg = shift;
  16.         my $ans = "";
  17.  
  18.         $ans = $username if ($code == PAM_PROMPT_ECHO_ON());
  19.         $ans = $password if ($code == PAM_PROMPT_ECHO_OFF());
  20.  
  21.         push @res, (PAM_SUCCESS(), $ans);
  22.      }
  23.      push @res, PAM_SUCCESS();

  24.      return @res;
  25.   };

  26.   my $pamh = new Authen::PAM('bootmenu', $username, $callback);

  27.   {
  28.      last unless ref $pamh;
  29.      last unless $pamh->pam_authenticate() == PAM_SUCCESS;
  30.      $success = 1;
  31.   }

  32.   return $success;
  33. }

  34. return 1;
  35. END


以上的代碼是一字不差是從 Authen::PAM::FAQ 的 man 頁面中複製來的。

重定義 bootmenu 應用程式,以使它僅當提供了有效的用戶名和密碼之後傳回一個網絡引導模板:

  1. # cat << 'END' > $MY_MOJO/bootmenu.pl
  2. #!/usr/bin/env perl

  3. use lib 'lib';

  4. use PAM;
  5. use Mojolicious::Lite;
  6. use Mojolicious::Plugins;
  7. use Mojo::Util ('url_unescape');

  8. plugin 'Config';

  9. get '/menu';
  10. get '/boot' => sub {
  11.   my $c = shift;

  12.   my $instance = $c->param('instance');
  13.   my $username = $c->param('username');
  14.   my $password = $c->param('password');

  15.   my $template = 'menu';

  16.   {
  17.      last unless $instance =~ /^fc[[:digit:]]{2}$/;
  18.      last unless $username =~ /^[[:alnum:]]+$/;
  19.      last unless PAM::auth($username, url_unescape($password));
  20.      $template = $instance;
  21.   }

  22.   return $c->render(template => $template);
  23. };

  24. app->start;
  25. END


bootmenu 應用程式現在查找 lib 命令去找到相應的 WorkingDirectory。但是,預設情況下,對於 systemd 單元它的工作目錄設置為服務器的 root 目錄。因此,你必須更新 systemd 單元去設置 WorkingDirectory 為 bootmenu 應用程式的根目錄:

  1. # sed -i "/^RuntimeDirectory=/ a WorkingDirectory=$MY_MOJO" /etc/systemd/system/bootmenu.service
  2. # systemctl daemon-reload

更新模塊去使用重定義後的 bootmenu 應用程式:

  1. # cd $MY_MOJO/templates
  2. # MY_BOOTMENU_SERVER=$(</etc/hostname)
  3. # MY_FEDORA_RELEASES="28 29"
  4. # for i in $MY_FEDORA_RELEASES; do echo '#!ipxe' > fc$i.html.ep; grep "^kernel\|initrd" menu.html.ep | grep "fc$i" >> fc$i.html.ep; echo "boot || chain https://$MY_BOOTMENU_SERVER/menu" >> fc$i.html.ep; sed -i "/^:f$i$/,/^boot /c :f$i\nlogin\nchain https://$MY_BOOTMENU_SERVER/boot?instance=fc$i\&username;=\${username}\&password;=\${password:uristring} || goto failed" menu.html.ep; done

上面的最後的命令將生成類似下麵的三個檔案:

menu.html.ep

  1. #!ipxe

  2. set timeout 5000

  3. :menu
  4. menu iPXE Boot Menu
  5. item --key 1 lcl 1. Microsoft Windows 10
  6. item --key 2 f29 2. RedHat Fedora 29
  7. item --key 3 f28 3. RedHat Fedora 28
  8. choose --timeout ${timeout} --default lcl selected || goto shell
  9. set timeout 0
  10. goto ${selected}

  11. :failed
  12. echo boot failed, dropping to shell...
  13. goto shell

  14. :shell
  15. echo type 'exit' to get the back to the menu
  16. set timeout 0
  17. shell
  18. goto menu

  19. :lcl
  20. exit

  21. :f29
  22. login
  23. chain https://server-01.example.edu/boot?instance=fc29&username;=${username}&password;=${password:uristring} || goto failed

  24. :f28
  25. login
  26. chain https://server-01.example.edu/boot?instance=fc28&username;=${username}&password;=${password:uristring} || goto failed


fc29.html.ep

  1. #!ipxe
  2. 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
  3. initrd --name initrd.img ${prefix}/initramfs-4.19.5-300.fc29.x86_64.img
  4. boot || chain https://server-01.example.edu/menu

fc28.html.ep

  1. #!ipxe
  2. 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
  3. initrd --name initrd.img ${prefix}/initramfs-4.19.3-200.fc28.x86_64.img
  4. boot || chain https://server-01.example.edu/menu

現在,重啟動 bootmenu 應用程式,並驗證用戶認證是否正常工作:

  1. # systemctl restart bootmenu.service

使得 iSCSI Target 可寫

現在,用戶驗證通過 iPXE 可以正常工作,在用戶連接時,你可以按需在只讀鏡像的上面創建每用戶可寫的overlay疊加層。使用一個 寫時複製[2] 的疊加層與簡單地為每個用戶複製原始鏡像相比有三個好處:

1. 副本創建非常快。這樣就可以按需創建。
2. 副本並不增加服務器上的磁盤使用。除了原始鏡像之外,僅儲存用戶寫入個人鏡像的內容。
3. 由於每個副本的扇區大多都是服務器的儲存器上的相同扇區,在隨後的用戶訪問這些操作系統的副本時,它們可能已經加載到記憶體中,這樣就提升了服務器的性能,因為對記憶體的訪問速度要比磁盤 I/O 快得多。

使用寫時複製的一個潛在隱患是,一旦疊加層創建後,疊加層之下的鏡像就不能再改變。如果它們改變,所有它們之上的疊加層將出錯。因此,疊加層必須被刪除並用新的、空白的進行替換。即便只是簡單地以讀寫樣式加載的鏡像,也可能因為某些檔案系統更新導致疊加層出錯。

由於這個隱患,如果原始鏡像被修改將導致疊加層出錯,因此運行下列的命令,將原始鏡像標記為不可改變:

  1. # chattr +i </path/to/file>

你可以使用 lsattr

 去查看不可改變標誌,並可以使用 chattr -i  取消設置不可改變標誌。在設置了不可改變標誌之後,即便是 root 用戶或以 root 運行的系統行程也不修改或刪除這個檔案。

停止 tgtd.service 之後,你就可以改變鏡像檔案:

  1. # systemctl stop tgtd.service

當仍有連接打開的時候,運行這個命令一般需要一分鐘或更長的時間。

現在,移除只讀的 iSCSI 出口。然後更新模板中的 readonly-root 配置檔案,以使鏡像不再是只讀的:

  1. # MY_FC=fc29
  2. # rm -f /etc/tgt/conf.d/$MY_FC.conf
  3. # TEMP_MNT=$(mktemp -d)
  4. # mount /$MY_FC.img $TEMP_MNT
  5. # sed -i 's/^READONLY=yes$/READONLY=no/' $TEMP_MNT/etc/sysconfig/readonly-root
  6. # sed -i 's/^Storage=volatile$/#Storage=auto/' $TEMP_MNT/etc/systemd/journald.conf
  7. # umount $TEMP_MNT

將 journald 日誌從發送到記憶體修改回預設值(如果 /var/log/journal 存在的話記錄到磁盤),因為一個用戶報告說,他的客戶端由於應用程式生成了大量的系統日誌而產生記憶體上限溢位錯誤,導致它的客戶端被卡住。而將日誌記錄到磁盤的負面影響是客戶端產生了額外的寫入流量,這將在你的網絡引導服務器上可能增加一些沒有必要的 I/O。你應該去決定到底使用哪個選擇 —— 記錄到記憶體還是記錄到硬碟 —— 哪個更合適取決於你的環境。

因為你的模板鏡像在以後不能做任何的更改,因此在它上面設置不可更改標誌,然後重啟動 tgtd.service:

  1. # chattr +i /$MY_FC.img
  2. # systemctl start tgtd.service

現在,更新 bootmenu 應用程式:

  1. # cat << 'END' > $MY_MOJO/bootmenu.pl
  2. #!/usr/bin/env perl

  3. use lib 'lib';

  4. use PAM;
  5. use Mojolicious::Lite;
  6. use Mojolicious::Plugins;
  7. use Mojo::Util ('url_unescape');

  8. plugin 'Config';

  9. get '/menu';
  10. get '/boot' => sub {
  11.   my $c = shift;

  12.   my $instance = $c->param('instance');
  13.   my $username = $c->param('username');
  14.   my $password = $c->param('password');

  15.   my $chapscrt;
  16.   my $template = 'menu';

  17.   {
  18.      last unless $instance =~ /^fc[[:digit:]]{2}$/;
  19.      last unless $username =~ /^[[:alnum:]]+$/;
  20.      last unless PAM::auth($username, url_unescape($password));
  21.      last unless $chapscrt = `sudo scripts/mktgt $instance $username`;
  22.      $template = $instance;
  23.   }

  24.   return $c->render(template => $template, username => $username, chapscrt => $chapscrt);
  25. };

  26. app->start;
  27. END


新版本的 bootmenu 應用程式呼叫一個定製的 mktgt 腳本,如果成功,它將為每個它自己創建的新的 iSCSI 標的傳回一個隨機的 CHAP[3] 密碼。這個 CHAP 密碼可以防止其它用戶的 iSCSI 標的以間接方式掛載這個用戶的標的。這個應用程式只有在用戶密碼認證成功之後才傳回一個正確的 iSCSI 標的密碼。

mktgt 腳本要加 sudo 前綴來運行,因為它需要 root 權限去創建標的。

$username 和 $chapscrt 變數也傳遞給 render 命令,因此在需要的時候,它們也能夠被納入到模板中傳回給用戶。

接下來,更新我們的引導模板,以便於它們能夠讀取用戶名和 chapscrt 變數,並傳遞它們到所屬的終端用戶。也要更新模板以 rw(讀寫)樣式加載根檔案系統:

  1. # cd $MY_MOJO/templates
  2. # sed -i "s/:$MY_FC/:$MY_FC-/g" $MY_FC.html.ep
  3. # sed -i "s/ netroot=iscsi:/ netroot=iscsi::@/" $MY_FC.html.ep
  4. # sed -i "s/ ro / rw /" $MY_FC.html.ep

運行上面的命令後,你應該會看到如下的引導模板:

  1. #!ipxe
  2. 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- $username %>-lun-1 netroot=iscsi: $username %>: $chapscrt %>@192.0.2.158::::iqn.edu.example.server-01:fc29- $username %> console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet
  3. initrd --name initrd.img ${prefix}/initramfs-4.19.5-300.fc29.x86_64.img
  4. boot || chain https://server-01.example.edu/menu

註意:如果在 插入[4] 變數後需要查看引導模板,你可以在 boot 命令之前,在它自己的行中插入 shell 命令。然後在你網絡引導你的客戶端時,iPXE 將在那裡給你提供一個用於交互的 shell,你可以在 shell 中輸入 imgstat 去查看傳遞到內核的引數。如果一切正確,你可以輸入 exit 去退出 shell 並繼續引導過程。

現在,通過 sudo 允許 bootmenu 用戶以 root 權限去運行 mktgt 腳本(僅這個腳本):

  1. # echo "bootmenu ALL = NOPASSWD: $MY_MOJO/scripts/mktgt *" > /etc/sudoers.d/bootmenu

bootmenu 用戶不應該寫訪問 mktgt 腳本或在它的家目錄下的任何其它檔案。在 /opt/bootmenu 目錄下的所有檔案的屬主應該是 root,並且不應該被其它任何 root 以外的用戶可寫。

sudo 在使用 systemd 的 DynamicUser 選項下不能正常工作,因此創建一個普通用戶帳戶,並設置 systemd 服務以那個用戶運行:

  1. # useradd -r -c 'iPXE Boot Menu Service' -d /opt/bootmenu -s /sbin/nologin bootmenu
  2. # sed -i 's/^DynamicUser=true$/User=bootmenu/' /etc/systemd/system/bootmenu.service
  3. # systemctl daemon-reload

最後,為寫時複製改寫創建一個目錄,並創建管理 iSCSI 標的的 mktgt 腳本和它們的改寫支持儲存:

  1. # mkdir /$MY_FC.cow
  2. # mkdir $MY_MOJO/scripts
  3. # cat << 'END' > $MY_MOJO/scripts/mktgt
  4. #!/usr/bin/env perl

  5. # if another instance of this script is running, wait for it to finish
  6. "$ENV{FLOCKER}" eq 'MKTGT' or exec "env FLOCKER=MKTGT flock /tmp $0 @ARGV";

  7. # use "RETURN" to print to STDOUT; everything else goes to STDERR by default
  8. open(RETURN, '>&', STDOUT);
  9. open(STDOUT, '>&', STDERR);

  10. my $instance = shift or die "instance not provided";
  11. my $username = shift or die "username not provided";

  12. my $img = "/$instance.img";
  13. my $dir = "/$instance.cow";
  14. my $top = "$dir/$username";

  15. -f "$img" or die "'$img' is not a file";
  16. -d "$dir" or die "'$dir' is not a directory";

  17. my $base;
  18. die unless $base = `losetup --show --read-only --nooverlap --find $img`;
  19. chomp $base;

  20. my $size;
  21. die unless $size = `blockdev --getsz $base`;
  22. chomp $size;

  23. # create the per-user sparse file if it does not exist
  24. if (! -e "$top") {
  25.   die unless system("dd if=/dev/zero of=$top status=none bs=512 count=0 seek=$size") == 0;
  26. }

  27. # create the copy-on-write overlay if it does not exist
  28. my $cow="$instance-$username";
  29. my $dev="/dev/mapper/$cow";
  30. if (! -e "$dev") {
  31.   my $over;
  32.   die unless $over = `losetup --show --nooverlap --find $top`;
  33.   chomp $over;
  34.   die unless system("echo 0 $size snapshot $base $over p 8 | dmsetup create $cow") == 0;
  35. }

  36. my $tgtadm = '/usr/sbin/tgtadm --lld iscsi';

  37. # get textual representations of the iscsi targets
  38. my $text = `$tgtadm --op show --mode target`;
  39. my @targets = $text =~ /(?:^T.*\n)(?:^ .*\n)*/mg;

  40. # convert the textual representations into a hash table
  41. my $targets = {};
  42. foreach (@targets) {
  43.   my $tgt;
  44.   my $sid;

  45.   foreach (split /\n/) {
  46.      /^Target (\d+)(?{ $tgt = $targets->{$^N} = [] })/;
  47.      /I_T nexus: (\d+)(?{ $sid = $^N })/;
  48.      /Connection: (\d+)(?{ push @{$tgt}, [ $sid, $^N ] })/;
  49.   }
  50. }

  51. my $hostname;
  52. die unless $hostname = `hostname`;
  53. chomp $hostname;

  54. my $target = 'iqn.' . join('.', reverse split('\.', $hostname)) . ":$cow";

  55. # find the target id corresponding to the provided target name and
  56. # close any existing connections to it
  57. my $tid = 0;
  58. foreach (@targets) {
  59.   next unless /^Target (\d+)(?{ $tid = $^N }): $target$/m;
  60.   foreach (@{$targets->{$tid}}) {
  61.      die unless system("$tgtadm --op delete --mode conn --tid $tid --sid $_->[0] --cid $_->[1]") == 0;
  62.   }
  63. }

  64. # create a new target if an existing one was not found
  65. if ($tid == 0) {
  66.   # find an available target id
  67.   my @ids = (0, sort keys %{$targets});
  68.   $tid = 1; while ($ids[$tid]==$tid) { $tid++ }

  69.   # create the target
  70.   die unless -e "$dev";
  71.   die unless system("$tgtadm --op new --mode target --tid $tid --targetname $target") == 0;
  72.   die unless system("$tgtadm --op new --mode logicalunit --tid $tid --lun 1 --backing-store $dev") == 0;
  73.   die unless system("$tgtadm --op bind --mode target --tid $tid --initiator-address ALL") == 0;
  74. }

  75. # (re)set the provided target's chap password
  76. my $password = join('', map(chr(int(rand(26))+65), 1..8));
  77. my $accounts = `$tgtadm --op show --mode account`;
  78. if ($accounts =~ / $username$/m) {
  79.   die unless system("$tgtadm --op delete --mode account --user $username") == 0;
  80. }
  81. die unless system("$tgtadm --op new --mode account --user $username --password $password") == 0;
  82. die unless system("$tgtadm --op bind --mode account --tid $tid --user $username") == 0;

  83. # return the new password to the iscsi target on stdout
  84. print RETURN $password;
  85. END
  86. # chmod +x $MY_MOJO/scripts/mktgt


上面的腳本將做以下五件事情:

1. 創建 /.cow/ 稀疏檔案(如果不存在的話)。
2. 創建 /dev/mapper/- 設備節點作為 iSCSI 標的的寫時複製支持儲存(如果不存在的話)。
3. 創建 iqn.:- iSCSI 標的(如果不存在的話)。或者,如果已存在了,它將關閉任何已存在的連接,因為在任何時刻,鏡像只能以只讀樣式從一個地方打開。
4. 它在 iqn.:- iSCSI 標的上(重新)設置 chap 密碼為一個新的隨機值。
5. (如果前面的所有任務都成功的話)它在 標準輸出[5] 上顯示新的 chap 密碼。

你應該可以在命令列上通過使用有效的測試引數來運行它,以測試 mktgt 腳本能否正常工作。例如:

  1. # echo `$MY_MOJO/scripts/mktgt fc29 jsmith`

當你從命令列上運行時,mktgt 腳本應該會輸出 iSCSI 標的的一個隨意的八字符隨機密碼(如果成功的話)或者是出錯位置的行號(如果失敗的話)。

有時候,你可能需要在不停止整個服務的情況下刪除一個 iSCSI 標的。例如,一個用戶可能無意中損壞了他的個人鏡像,在那種情況下,你可能需要按步驟撤銷上面的 mktgt 腳本所做的事情,以便於他下次登入時他將得到一個原始鏡像。

下麵是用於撤銷的 rmtgt 腳本,它以相反的順序做了上面 mktgt 腳本所做的事情:

  1. # mkdir $HOME/bin
  2. # cat << 'END' > $HOME/bin/rmtgt
  3. #!/usr/bin/env perl

  4. @ARGV >= 2 or die "usage: $0 [+d|+f]\n";

  5. my $instance = shift;
  6. my $username = shift;

  7. my $rmd = ($ARGV[0] eq '+d'); #remove device node if +d flag is set
  8. my $rmf = ($ARGV[0] eq '+f'); #remove sparse file if +f flag is set
  9. my $cow = "$instance-$username";

  10. my $hostname;
  11. die unless $hostname = `hostname`;
  12. chomp $hostname;

  13. my $tgtadm = '/usr/sbin/tgtadm';
  14. my $target = 'iqn.' . join('.', reverse split('\.', $hostname)) . ":$cow";

  15. my $text = `$tgtadm --op show --mode target`;
  16. my @targets = $text =~ /(?:^T.*\n)(?:^ .*\n)*/mg;

  17. my $targets = {};
  18. foreach (@targets) {
  19.   my $tgt;
  20.   my $sid;

  21.   foreach (split /\n/) {
  22.      /^Target (\d+)(?{ $tgt = $targets->{$^N} = [] })/;
  23.      /I_T nexus: (\d+)(?{ $sid = $^N })/;
  24.      /Connection: (\d+)(?{ push @{$tgt}, [ $sid, $^N ] })/;
  25.   }
  26. }

  27. my $tid = 0;
  28. foreach (@targets) {
  29.   next unless /^Target (\d+)(?{ $tid = $^N }): $target$/m;
  30.   foreach (@{$targets->{$tid}}) {
  31.      die unless system("$tgtadm --op delete --mode conn --tid $tid --sid $_->[0] --cid $_->[1]") == 0;
  32.   }
  33.   die unless system("$tgtadm --op delete --mode target --tid $tid") == 0;
  34.   print "target $tid deleted\n";
  35.   sleep 1;
  36. }

  37. my $dev = "/dev/mapper/$cow";
  38. if ($rmd or ($rmf and -e $dev)) {
  39.   die unless system("dmsetup remove $cow") == 0;
  40.   print "device node $dev deleted\n";
  41. }

  42. if ($rmf) {
  43.   my $sf = "/$instance.cow/$username";
  44.   die "sparse file $sf not found" unless -e "$sf";
  45.   die unless system("rm -f $sf") == 0;
  46.   die unless not -e "$sf";
  47.   print "sparse file $sf deleted\n";
  48. }
  49. END
  50. # chmod +x $HOME/bin/rmtgt


例如,使用上面的腳本去完全刪除 fc29-jsmith 標的,包含它的支持儲存設備節點和稀疏檔案,可以按下列方式運行命令:

  1. # rmtgt fc29 jsmith +f

一旦你驗證 mktgt 腳本工作正常,你可以重啟動 bootmenu 服務。下次有人從網絡引導時,他們應該能夠接收到一個他們可以寫入的、可”私人定製“的網絡引導鏡像的副本:

  1. # systemctl restart bootmenu.service

現在,就像下麵的截屏示範的那樣,用戶應該可以修改根檔案系統了:


via: https://fedoramagazine.org/how-to-build-a-netboot-server-part-4/

作者:Gregory Bartholomew[7] 選題:lujun9972 譯者:qhwdw 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

赞(0)

分享創造快樂