#!/usr/bin/perl -w # Drakwizard PXE # release 0.3 # Copyright (C) 2004 Mandrakesoft # # Author: Antoine Ginies # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. package MDK::Wizard::Pxe; use lib qw(/usr/lib/libDrakX); use strict; use common; use services; use MDK::Wizard::Wizcommon; my $wiz = new MDK::Wizard::Wizcommon; #my $INSTALLDIR = "/var/install/pxe"; #my $IA64PATH = '/IA64PC'; my $TMPDIR = '/tmp'; my $HOSTNAME = chomp_(`hostname`); my $TFTPDIR = "/var/lib/tftpboot"; my $CLIENTPATH = '/X86PC/linux'; my $X86 = $TFTPDIR . $CLIENTPATH; my $IMGPATH = $X86 . '/images'; my $PXEHELP = $X86 . '/help.txt'; my $PXEMESSAGE = $X86 . '/messages'; my $PXEDEFAULT = $X86 . '/pxelinux.cfg/default'; my $pxeconf = '/etc/pxe.conf'; my $SYSLINUXPATH = '/usr/lib/syslinux/'; my $MEMDISK = $SYSLINUXPATH . '/memdisk'; my $XINETDDIR = "/etc/xinetd.d"; my $IPSERVER = $wiz->{net}->itf_get("IPADDR"); if (!$IPSERVER) { my $interface = 'eth0'; ($IPSERVER) = `/sbin/ip addr show dev $interface` =~ /^\s*inet\s+(\d+\.\d+\.\d+\.\d+)/m; } #my $DOMAINNAME = $wiz->{net}->network_get("DOMAINNAME"); my $DOMAINNAME = chomp_(`dnsdomainname`); my $o = { name => N("PXE Wizard"), allow_user => 'root', var => { IMG => '/var/install/9.2/images/network.img', PXENAME => '92', DESCR => 'install 9.2', IMGTOREMOVE => '', IMGTOMODIFY => '', IP => '127.0.0.1', DIR => '/var/install/', ALLRDZ => '/mnt/cdrom/isolinux/alt0/all.rdz', VMLINUZ => '/mnt/cdrom/isolinux/alt0/vmlinuz', OPTION => '', i => '', }, needed_rpm => [ 'pxe', 'tftp-server', 'xinetd' ], init => sub { if (member($DOMAINNAME, qw(localdomain (none)))) { return 0, N("You need to readjust your domainname, not equal to localdomain or none. Please launch drakconnect to adjust it.") } 1 }, }; my %level = ( 1 => N("Set PXE server"), 2 => N("Add boot image in PXE"), 3 => N("Remove boot image in PXE"), 4 => N("Modify boot image in PXE"), 5 => N("Add all.rdz image in PXE"), ); # to get list menu entry my @list_menu; foreach (cat_($PXEDEFAULT)) { my ($ent) = /# TAG: (\w+)_BEGIN/; ! $ent or push @list_menu,$ent; } #set default list for entry in syslinux.cfg my @list_method = qw(nfs http); my @list_ram = qw(32000 48000 64000 1280000); my @list_vga = qw(788 normal 785 791 792 text); my @list_acpi = qw(ht force off); my @list_apic = qw(apic noapic); my @list_eth = qw(eth0 eth1 eth2); #begin of wizard $o->{pages} = { welcome => { name => N("PXE wizard") . "\n\n" . N("Set a PXE server.") . "\n" . N("This wizard will help you configuring the PXE server, and PXE boot image management. PXE (Pre-boot eXecution Environment) is a protocol designed by Intel that allows computers to boot through the network. PXE is stored in the ROM of new generation network cards. When the computer boots up, the BIOS loads the PXE ROM in the memory and executes it. A menu is displayed, allowing the computer to boot an operating system loaded through the network."), no_back => 1, pre => sub { $o->{var}{wiz_level} ||= 1; }, post => sub { if (! -f "$X86/drakwizard_pxe") { return 'pxeserver' } if ($o->{var}{wiz_level} == 1) { return 'pxeserver' } elsif ($o->{var}{wiz_level} == 2) { return 'addimg' } elsif ($o->{var}{wiz_level} == 5) { return 'addimgrdz' } @list_menu or return 'needimg'; if ($o->{var}{wiz_level} == 3) { return 'removeimg' } elsif ($o->{var}{wiz_level} == 4) { return 'modifyimg' } }, data => [ { label => N("What do you want to do:"), val => \$o->{var}{wiz_level}, list => [ keys %level ], format => sub { $level{$_[0]} } }, ], no_back => 1, next => 'pxeserver', }, addimg => { name => N("Add a boot image") . "\n\n" . N("To boot through network, the network computer needs a boot image. Morever we need to name this image, so each boot image is related to a name in PXE menu. User can then choose which image he wants to boot through PXE.") . "\n\n" . N("PXE description is used to explain the rule of the boot image, ie: Mandrake 10 image, Mandrake cooker image..") . "\n\n" . N("Path to image: provide the full path to the network boot image") . "\n\n" . N("PXE name: the name displayed in PXE menu (please provide a word or a number, with no space)"), data => [ { label => "PXE name:", val => \$o->{var}{PXENAME} }, { label => "PXE description:", val => \$o->{var}{DESCR} }, { label => "Full path to boot image:", val => \$o->{var}{IMG} }, ], post => \&test_data, next => 'summaryadd', }, addimgrdz => { name => N("Add all.rdz boot image") . "\n\n" . N("To boot through network, network computer need a boot image. Morever we need to name this image, so each boot image is related to a name in PXE menu. So user can choose wich image he wants to boot through PXE.") . "\n\n" . N("PXE description is used to explain the rule of the boot image, ie: Mandrake 10 image, Mandrake cooker image..") . N("For technical reason, in case of multiple boot image, it's more simple to boot network computer through a kernel (vmlinuz), and provide one file with all drivers needed (in our case all.rdz).") . "\n\n" . N("Path to all.rdz: provide the full path to all.rdz image") . "\n\n" . N("PXE name: the name displayed in PXE menu (please provide a word or a number, with no space)"), data => [ { label => "PXE name:", val => \$o->{var}{PXENAME} }, { label => "PXE description:", val => \$o->{var}{DESCR} }, { label => "Full path to all.rdz image:", val => \$o->{var}{ALLRDZ} }, { label => "Full path to vmlinuz:", val => \$o->{var}{VMLINUZ} }, ], post => \&test_datardz, next => 'summaryaddrdz', }, removeimg => { name => N("Remove a boot image") . "\n\n" . N("Please choose the PXE boot image you want to remove from the PXE server.") . "\n\n" . N("The PXE boot image, and the related entry in PXE menu will be deleted."), data => [ { label => N("Boot image to remove:"), val => \$o->{var}{IMGTOREMOVE}, fixed_list => \@list_menu }, ], no_back => 1, next => 'summaryremove', }, modifyimg => { name => N("Add options to boot image") . "\n\n" . N("In some cases, you want to add some options to the PXE boot image. This wizard provides an easy way to customize a boot image with common parameters.") . "\n\n" . N("Please choose, from the list below, the PXE boot image you want to modify"), data => [ { label => N("Boot image to modify:"), val => \$o->{var}{IMGTOMODIFY}, fixed_list => \@list_menu }, ], post => \&read_option, no_back => 1, next => 'gomodify', }, gomodify => { name => N("Add option to the PXE boot disk") . "\n\n" . N("Server IP: IP address of the server, which contains installation directory. You can create one with MDK install server wizard.") . "\n" . N("Install directory: the full path to MDK install server directory") . "\n" . N("Installation method: choose NFS or HTTP."), data => [ { label => N("Boot image to modify:"), fixed_val => \$o->{var}{i} }, { label => N("Server IP:"), val => \$o->{var}{IP} }, { label => N("Install directory:"), val => \$o->{var}{DIR} }, { label => N("Installation method:"), val => \$o->{var}{METHOD}, fixed_list => \@list_method }, ], next => 'gomodify2', }, gomodify2 => { name => N("Network client interface: the network interface used for the installation process.") . "\n" . N("Ramsize: adjust the ramsize parameter on boot disk.") . "\n" . N("VGA option: if you encounter any problems with VGA, please adjust. ") . "\n" . N("ACPI option: Advanced Configuration and Power Interface"), data => [ { label => N("Network client interface:"), val => \$o->{var}{ETH}, fixed_list => \@list_eth }, { label => N("Ramsize:"), val => \$o->{var}{RAM}, fixed_list => \@list_ram }, { label => N("VGA option:"), val => \$o->{var}{VGA}, fixed_list => \@list_vga }, { label => N("ACPI option:"), val => \$o->{var}{ACPI}, fixed_list => \@list_acpi }, { label => N("APIC option:"), val => \$o->{var}{APIC}, fixed_list => \@list_apic }, { label => N("Custom option:"), val => \$o->{var}{OPTION} }, ], next => 'summarymodify', }, pxeserver => { name => N("Set PXE server") . "\n\n" . N("We need to use a special dhcpd.conf file with PXE parameter. To set up such a DHCP server, launch the DHCP wizard and check the box 'Enable PXE'. If you don't do that, PXE query will not be answered by this server.") . "\n" . N("Now the wizard will configure all needed default configuration files to allow computers to boot through the network."), next => 'summaryserver', }, error_img => { name => N("Please provide a bootable image. To boot through a network, network computers need a boot image."), next => 'addimg', }, needimg => { name => N("We need an all.rdz or a network.img image. Please add one."), no_back => 1, next => 'welcome', post => sub { $o->{var}{wiz_level} = 5 }, }, error_imgrdz => { name => N("Please provide an all.rdz image, which contains all drivers. You can find one on the first CD of the Mandrakelinux distribution, in the /isolinux/alt0/ directory."), next => 'addimgrdz', }, error_path_img => { name => N("Please choose an image from a different directory than %s.", $IMGPATH), next => 'addimg', }, error_path_imgrdz => { name => N("Please choose an image from a different directory than %s.", $IMGPATH), next => 'addimgrdz', }, error_name => { name => N("Please provide a correct PXE name: one word or one number with no space."), next => 'addimg', }, error_needpxe => { name => N("To add/remove/modify PXE boot image, you need to run 'Set PXE server' before."), no_back => 1, next => 'pxeserver', }, simmilar_entry => { name => N("A similar name is already used in PXE menu") . "\n\n" . N("Please provide another one."), next => 'addimg', }, simmilar_entryrdz => { name => N("A similar name is already used in PXE menu") . "\n\n" . N("Please provide another one."), next => 'addimgrdz', }, summaryserver => { name => N("The wizard will now prepare all default files to set your PXE server"), data => [ { label => N("TFTP directory:"), fixed_val => \$TFTPDIR }, { label => N("Boot image path:"), fixed_val => \$IMGPATH }, { label => N("PXE 'default' file:"), fixed_val => \$PXEDEFAULT }, { label => N("PXE 'help' file:"), fixed_val => \$PXEHELP }, ], post => \&do_it_pxe, next => 'endserver', }, summarymodify => { name => N("The wizard will now modify boot options with those parameters:"), data => [ { label => N("Boot image to modify:"), fixed_val => \$o->{var}{i} }, { label => N("Server IP:"), fixed_val => \$o->{var}{IP} }, { label => N("Install directory:"), fixed_val => \$o->{var}{DIR} }, { label => N("Installation method:"), fixed_val => \$o->{var}{METHOD} }, { label => N("Network client interface:"), fixed_val => \$o->{var}{ETH} }, { label => N("Ramsize:"), fixed_val => \$o->{var}{RAM} }, { label => N("VGA option:"), fixed_val => \$o->{var}{VGA} }, { label => N("ACPI option:"), fixed_val => \$o->{var}{ACPI} }, { label => N("APIC option:"), fixed_val => \$o->{var}{APIC} }, { label => N("Custom option:"), fixed_val => \$o->{var}{OPTION} }, ], post => \&do_it_modify, next => 'endmodify', }, summaryremove => { name => N("The wizard will now remove this PXE boot image"), data => [ { label => N("PXE entry to remove:"), fixed_val => \$o->{var}{IMGTOREMOVE} }, ], no_back => 1, post => \&do_it_remove, next => 'endremove', }, summaryadd => { name => N("The wizard will now add this PXE boot image"), data => [ { label => "PXE name:", fixed_val => \$o->{var}{PXENAME} }, { label => "PXE description:", fixed_val => \$o->{var}{DESCR} }, { label => "Path to boot image:", fixed_val => \$o->{var}{IMG} }, ], post => \&do_it_add, next => 'endadd', }, summaryaddrdz => { name => N("The wizard will now add this PXE boot image"), data => [ { label => "PXE name:", fixed_val => \$o->{var}{PXENAME} }, { label => "PXE description:", fixed_val => \$o->{var}{DESCR} }, { label => "Full path to all.rdz image:", fixed_val => \$o->{var}{ALLRDZ} }, { label => "Full path to vmlinuz:", fixed_val => \$o->{var}{VMLINUZ} }, ], post => \&do_it_addrdz, next => 'endadd', }, endadd => { name => N("Congratulations"), data => [ { label => N("The wizard successfully added the PXE boot image.") } ], post => sub { @list_menu = (); foreach (cat_($PXEDEFAULT)) { my ($ent) = /# TAG: (\w+)_BEGIN/; ! $ent or push @list_menu,$ent; } }, next => 'welcome', }, endremove => { name => N("Congratulations"), data => [ { label => N("The wizard successfully removed the PXE boot image.") } ], post => sub { @list_menu = (); foreach (cat_($PXEDEFAULT)) { my ($ent) = /# TAG: (\w+)_BEGIN/; ! $ent or push @list_menu,$ent; } }, next => 'welcome', }, endmodify => { name => N("Congratulations"), data => [ { label => N("The wizard successfully modified the boot option.") } ], next => 'welcome', }, endserver => { name => N("Congratulations"), data => [ { label => N("The wizard successfully configured your PXE server.") } ], no_back => 1, end => 1, next => 0 }, error_end => { name => N("Failed"), data => [ { label => N("Please relaunch drakwizard, and try to change some parameters.") } ], no_back => 1, end => 1, next => 0, }, }; #cp memlinux from syslinux sub memlinux_prep { if (!-f "$X86/memdisk") { cp_af($MEMDISK, $X86); } } # test img and similar entry in PXE sub test_data { $o->{var}{PXENAME} =~ /^\w+$/ or return 'error_name'; if (!-f $o->{var}{IMG}) { return 'error_img' } if (any { /^$o->{var}{PXENAME}/ } cat_($PXEHELP)) { return 'simmilar_entry' } } sub test_datardz { $o->{var}{PXENAME} =~ /^\w+$/ or return 'error_name'; if (!-f $o->{var}{ALLRDZ}) { return 'error_imgrdz' } if (!-f $o->{var}{VMLINUZ}) { return 'error_imgrdz' } if (any { /^$o->{var}{PXENAME}/ } cat_($PXEHELP)) { return 'simmilar_entryrdz' } } # enable tftp server sub enable_tftps { substInFile { s/disable.*/disable = no/ } "$XINETDDIR/tftp"; } # can change option in tftp server sub tftp_blksize { # $o should be with W or not N my ($o) = @_; if ($o =~ /W/) { substInFile { s/server_args.*/server_args = -r blksize -s $TFTPDIR/ } "$XINETDDIR/tftp"; } else { substInFile { s/server_args.*/server_args = -s $TFTPDIR/ } "$XINETDDIR/tftp"; } } sub crea_wdir { if (-e $TMPDIR) { rm_rf($TMPDIR); } mkdir_p($TMPDIR); } # add TAGGED entry in PXE menu sub add_in_default { my ($NAME) = @_; if (!any { /${NAME}_BEGIN/ } cat_($PXEDEFAULT)) { append_to_file($PXEDEFAULT, <{var}{PXENAME}, $o->{var}{DESCR}); add_in_default($o->{var}{PXENAME}); add_img($o->{var}{IMG}, $o->{var}{PXENAME}); } sub do_it_addrdz { return if $::testing; add_in_help($o->{var}{PXENAME}, $o->{var}{DESCR}); add_in_defaultrdz($o->{var}{PXENAME}); add_imgrdz($o->{var}{ALLRDZ}, $o->{var}{VMLINUZ}, $o->{var}{PXENAME}); } # main remove image sub do_it_remove { return if $::testing; @list_menu or return 'needimg'; remove_in_menu($o->{var}{IMGTOREMOVE}); remove_in_help($o->{var}{IMGTOREMOVE}); remove_img($o->{var}{IMGTOREMOVE}); } # get default option for an image sub read_option { my $IMG = $o->{var}{IMGTOMODIFY}; # detect if need loop or just add option to APPEND line if (-f "$IMGPATH/$IMG.img") { my $mount = "/tmp/loop_for_images"; mkdir_p($mount); system("mount $IMGPATH/$IMG.img $mount -o loop 2>/dev/null"); my $line = cat_("$mount/syslinux.cfg"); $o->{var}{i} = "$IMGPATH/$IMG.img"; ($o->{var}{METHOD}, $o->{var}{ETH}, $o->{var}{IP}, $o->{var}{DIR}, $o->{var}{RAM}, $o->{var}{VGA}, $o->{var}{ACPI}, $o->{var}{APIC}, $o->{var}{OPTION}) = $line =~ m!method:(nfs|http),interface:(eth0|eth1|eth2),network:dhcp,server:(.*?),directory:(.*?) ramdisk_size=(.*?) root=/dev/ram3 rw vga=(.*?)acpi=(ht|force|off)\s(apic|noapic)\s(.*)!; system("umount $mount"); if (-e $mount) { rm_rf($mount); } } else { my $line = cat_($PXEDEFAULT); $o->{var}{i} = "$IMGPATH/$IMG.rdz"; ($o->{var}{METHOD}, $o->{var}{ETH}, $o->{var}{IP}, $o->{var}{DIR}, $o->{var}{RAM}, $o->{var}{VGA}, $o->{var}{ACPI}, $o->{var}{APIC}, $o->{var}{OPTION}) = $line =~ m!APPEND initrd=images/$IMG.rdz automatic=method:(nfs|http),interface:(eth0|eth1|eth2),network:dhcp,server:(.*?),directory:(.*?) ramdisk_size=(.*?) root=/dev/ram3 rw vga=(.*?)acpi=(ht|force|off)\s(apic|noapic)\s(.*)!; } } # modify default boot option of an image sub do_it_modify { @list_menu or return 'needimg'; my $IMG = $o->{var}{IMGTOMODIFY}; my ($METHOD, $ETH, $IP, $DIR, $RAM, $VGA, $ACPI, $APIC, $OPTION) = ($o->{var}{METHOD}, $o->{var}{ETH}, $o->{var}{IP}, $o->{var}{DIR}, $o->{var}{RAM}, $o->{var}{VGA}, $o->{var}{ACPI}, $o->{var}{APIC}, $o->{var}{OPTION}); return if $::testing; if (-f "$IMGPATH/$IMG.img") { $o->{var}{i} = "$IMGPATH/$IMG.img"; return if $::testing; #APPEND initrd=images/ken/cooker/i586/isolinux/alt0/all.rdz automatic=method:nfs,interface:eth0,network:dhcp,server:192.168.100.7,directory:/c/cooker ramdisk_size=32000 root=/dev/ram3 rw vga=788 meta_class=discovery my $mount = "/tmp/loop_for_images"; mkdir_p($mount); system("mount $o->{var}{i} $mount -o loop"); output("$mount/syslinux.cfg", <{var}{i} = "$IMGPATH/$IMG.rdz"; substInFile { s!APPEND initrd=images/$IMG.rdz.*!APPEND initrd=images/$IMG.rdz automatic=method:$METHOD,interface:$ETH,network:dhcp,server:$IP,directory:$DIR ramdisk_size=$RAM root=/dev/ram3 rw vga=$VGA acpi=$ACPI $APIC $OPTION!m; } $PXEDEFAULT; } } sub pxe_conf { if (!-f "$pxeconf.orig") { cp_af($pxeconf, "$pxeconf.orig") } substInFile { s/default_address.*/default_address=$IPSERVER/; s/mtftp_address.*/mtftp_address=$IPSERVER/; s/domain.*/domain=$DOMAINNAME/; } $pxeconf; } # set PXE server sub do_it_pxe { return if $::testing; my $in = 'interactive'->vnew('su', 'PXE server'); my $w = $in->wait_message(N("PXE server"), N("Configuring PXE server on your system...")); output("$X86/drakwizard_pxe", < $o, }, $class; } 1;