### Global options option domain-name "#!variable!domain!#"; option domain-name-servers #!variable!dns!#; authoritative; ddns-update-style none; # refer to RFC4758 for possible arch option values option arch code 93 = unsigned integer 16; subnet #!variable!network!# netmask 255.255.0.0 { default-lease-time 600; max-lease-time 1200; range #!variable!range!#; option routers #!variable!router!#; if option arch = 00:07 { filename "uefi/shim.efi"; } else { filename "pxelinux.0"; } next-server #!variable!router!#; } ### Alteeve's Niche! Inc. - Anvil! Intelligent Availability(tm) Platform # License: GPLv2 # Built: #!variable!date!# # Target: Network Install (PXE) # OS: #!variable!os!# # Machine: #!variable!say_type!# # NOTE: Do no use any non-ASCII characters in this kickstart script. # Install using text screens, most compatible with modest hardware, too. text ### TODO: Might want to remove this for Striker. # Agree to the eula eula --agreed # Don't run the "firstboot" tool, we should have everything configured. firstboot --disable # Reboot when the install is done. reboot # Install from the source Striker url --url=#!variable!url!# ### TODO: These should be configurable eventually. # Keyboard layouts keyboard #!variable!keyboard!# timezone #!variable!timezone!# # NOTE: DON'T CHANGE THIS WITHOUT TESTING! The Anvil! code makes system calls and parses output. Changing # this could alter what the program can parse. The language of the Anvil! itself (and this most things # users will see) is controlled in /etc/anvil/anvil.conf lang en_CA.UTF-8 # Network information network --hostname=#!variable!hostname!# # Root and admin passwords are '#!variable!password!#' rootpw --plaintext #!variable!password!# user --name=admin --password "#!variable!password!#" --plaintext --gecos "admin" --groups wheel # TEMPORARY: Set selinux to permissive #selinux --permissive # Partitioning plan is generated by the %pre script. %include /tmp/plan_partitions.out %packages @^server-product-environment @server-hardware-support %end %addon com_redhat_kdump --disable --reserve-mb='128' %end %anaconda pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty %end ############################################################################################################# # %ost, --nochroot scripts # ############################################################################################################# ### TODO: Might want to remove this pre-production. Useful for debugging until then. # Record all the install logs for future reference. %post --nochroot echo 'Copying all the anaconda related log files to /root/install/' mkdir -p /mnt/sysimage/root/install_logs/var mkdir -p /mnt/sysimage/root/install_logs/run rsync -av /tmp /mnt/sysimage/root/install_logs/ rsync -av /run/install /mnt/sysimage/root/install_logs/run/ rsync -av /var/log /mnt/sysimage/root/install_logs/var/ %end ############################################################################################################# # %pre scripts # ############################################################################################################# ### This is the small anaconda-friendly perl program that looks at the ### available storage and chooses a drive to install on. Then it generates ### the kickstart partition instructions and records them in: ### /tmp/plan_partitions.out ### NOTE: This must be copied from 'scripts/plan_partitions', don't edit directly *EXCEPT* to set the '$type' ### just below, which *MUST* be set, or the script will fail. # - #!variable!debug!# # - #!variable!type!# %pre --interpreter /bin/perl #!/bin/perl # # This script is designed to identify hard drives and decide where and how to partition it for installation # during a kickstart install. # # Exit codes; # 0 - Success # 1 - Target type not specified. # 2 - Failed to find a drive to install on. # # NOTE: This is restricted to what is available during an anaconda install session. That is to say, bare # minimum. # TODO: If multiple matching drives are found (same medium and size, build an appropriate RAID array. # TODO: in pre, wipefs on all disks to clear old LVM and DRBD data # use strict; use warnings; # Set to '1' for verbose output my $debug = #!variable!debug!#; ### NOTE: This must be set to 'striker', 'node' or 'dr'! Wither set '$type' or use the appropriate argument. my $type = "#!variable!type!#"; if ((defined $ARGV[0]) && ((lc($ARGV[0]) eq "striker") or (lc($ARGV[0]) eq "node") or (lc($ARGV[0]) eq "dr"))) { $type = $ARGV[0]; } if ($type =~ /striker/i) { print "-=] Finding install drive(s) for a Striker dashboard.\n"; $type = "striker"; } elsif ($type =~ /node/i) { print "-=] Finding install drive(s) for an Anvil! node.\n"; $type = "node"; } elsif ($type =~ /dr/i) { print "-=] Finding install drive(s) for a DR (disaster recovery) host.\n"; $type = "dr"; } else { print " [ Error ] - Target type not specified! Usage: ".$0." {striker,node,dr} "; exit(1); } my $device = {}; # We might want to add HCTL (Host:Channel:Target:Lun for SCSI) and/or SUBSYSTEMS later my $drives = {}; my $target = ""; my $lsblk = system_call("/bin/lsblk --bytes --paths --pairs --output NAME,RM,HOTPLUG,TYPE,SIZE,TRAN,ROTA"); foreach my $line (split/\n/, $lsblk) { ### NOTE: If a drive has no transport, is not removable, but is hotplugable and the device path is ### mmcblk0, it is probably an SDCard. It doesn't seem to be a directly divinable state. We ### don't currently plan to use them, but it might come to pass later. print __LINE__."; [ Debug ] - lsblk: [".$line."]\n" if $debug; my ($path, $removable, $hotplug, $type, $size, $transport, $rotational) = ($line =~ /NAME="(.*?)" RM="(\d)" HOTPLUG="(\d)" TYPE="(.*?)" SIZE="(\d+)" TRAN="(.*?)" ROTA="(\d)"/); print __LINE__."; [ Debug ] - Device: [".$path."], type: [".$type."], remvoable? [".$removable."], hotplug? [".$hotplug."], rotational? [".$rotational."], transport: [".$transport."], size: [".$size."]\n" if $debug; # Skip 'zramX' devices next if ($path =~ /^\/dev\/zram\d/); # Skip removable disks and anything that just isn't a disk at all. next if (($removable) or ($hotplug) or ($type ne "disk")); $device->{$path} = { type => $type, size => $size, transport => $transport, rotational => $rotational, }; my $hr_size = hr_size($device->{$path}{size}); $device->{$path}{hr_size} = $hr_size; if ($device->{$path}{rotational}) { if (not $device->{$path}{transport}) { print "Analyzing platter or virtual drive: [".$path."] of the size: [".$device->{$path}{size}." (".$device->{$path}{hr_size}.")]\n"; } else { print "Analyzing platter drive: [".$path."], using the transport: [".$device->{$path}{transport}."], of the size: [".$device->{$path}{size}." (".$device->{$path}{hr_size}.")]\n"; } } else { print "Analyzing solid-state drive: [".$path."], using the transport: [".$device->{$path}{transport}."], of the size: [".$device->{$path}{size}." (".$device->{$path}{hr_size}.")]\n"; } if (not exists $drives->{by_hr_size}{$hr_size}) { $drives->{by_hr_size}{$hr_size} = []; } push @{$drives->{by_hr_size}{$hr_size}}, $path; } ### Usage selection priority # on Striker, we'll simply use whatever is the biggest avalable drive. # on Node and DR, we'll prefer slowest first (rotational, sata before nvme/scsi), and smallest second. my $use_drive = ""; if ($type eq "striker") { my $biggest_size = 0; foreach my $path (sort {$a cmp $b} keys %{$device}) { print __LINE__."; [ Debug ] - path: [".$path."], ${path}::size: [".$device->{$path}{size}." (".hr_size($device->{$path}{size}).")] < biggest_size: [".$biggest_size." (".hr_size($biggest_size).")]\n" if $debug; if ($device->{$path}{size} > $biggest_size) { $biggest_size = $device->{$path}{size}; $use_drive = $path; print __LINE__."; [ Debug ] - use_drive: [".$use_drive."], biggest_size: [".$biggest_size." (".hr_size($biggest_size).")]\n" if $debug; } } if ($use_drive) { print "Selected the largest disk: [".$use_drive."], which has a capacity of: [".hr_size($device->{$use_drive}{size})."]\n"; } } else { # Node and DR are handled the same my $first_disk_seen = 0; my $smallest_size = 0; my $selected_is_platter = 0; foreach my $path (sort {$a cmp $b} keys %{$device}) { print __LINE__."; [ Debug ] - first_disk_seen: [".$first_disk_seen."], path: [".$path."], ${path}::rotational: [".$device->{$path}{rotational}."]\n" if $debug; if (not $first_disk_seen) { # Select this one $first_disk_seen = 1; $use_drive = $path; $smallest_size = $device->{$path}{size}; $selected_is_platter = $device->{$path}{rotational}; print __LINE__."; [ Debug ] - first_disk_seen: [".$first_disk_seen."], use_drive: [".$use_drive."], selected_is_platter: [".$selected_is_platter."], smallest_size: [".$smallest_size." (".hr_size($smallest_size).")]\n" if $debug; } elsif ($device->{$path}{rotational}) { # This takes priority print __LINE__."; [ Debug ] - selected_is_platter: [".$selected_is_platter."]\n" if $debug; if ($selected_is_platter) { # Was the previously seen drive bigger? print __LINE__."; [ Debug ] - ".$path."::size: [".$first_disk_seen." (".hr_size($first_disk_seen).")], smallest_size: [".$smallest_size." (".hr_size($smallest_size).")]\n" if $debug; if ($device->{$path}{size} < $smallest_size) { # This is smaller, use it. $use_drive = $path; $smallest_size = $device->{$path}{size}; print __LINE__."; [ Debug ] - use_drive: [".$use_drive."], smallest_size: [".$smallest_size." (".hr_size($smallest_size).")]\n" if $debug; } } else { # The previous drive is an SSD, so use this one regardless $use_drive = $path; $smallest_size = $device->{$path}{size}; $selected_is_platter = $device->{$path}{rotational}; print __LINE__."; [ Debug ] - use_drive: [".$use_drive."], selected_is_platter: [".$selected_is_platter."], smallest_size: [".$smallest_size." (".hr_size($smallest_size).")]\n" if $debug; } } elsif (not $selected_is_platter) { # This is an SSD, but we haven't seen a platter drive yet, so use it if it is # smaller. print __LINE__."; [ Debug ] - ".$path."::size: [".$first_disk_seen." (".hr_size($first_disk_seen).")], smallest_size: [".$smallest_size." (".hr_size($smallest_size).")]\n" if $debug; if ($device->{$path}{size} < $smallest_size) { # This is smaller, use it. $use_drive = $path; $smallest_size = $device->{$path}{size}; print __LINE__."; [ Debug ] - use_drive: [".$use_drive."], smallest_size: [".$smallest_size." (".hr_size($smallest_size).")]\n" if $debug; } } } # Did we find a drive? if ($use_drive) { if ($selected_is_platter) { print "Selected the smallest platter drive: [".$use_drive."], which has a capacity of: [".hr_size($device->{$use_drive}{size})."]\n"; } else { print "Selected the smallest solid-state drive: [".$use_drive."], which has a capacity of: [".hr_size($device->{$use_drive}{size})."] (no platter drives found)\n"; } } } # Did we find a disk to use? if (not $use_drive) { print "[ Error ] - Failed to find any fixed drives (platter or USB, not removable) to install onto. Unable to proceed.\n"; exit(2); } # Pick up a bit of a UUID to add to the volume group name. my $id = time; if ((-e "/sys/class/dmi/id/product_uuid") && (-r "/sys/class/dmi/id/product_uuid")) { # We should be able to read the system UUID. If so, we'll take the starting part of the string for # the short ID. my $uuid = ""; my $shell_call = "/sys/class/dmi/id/product_uuid"; print __LINE__."; [ Debug ] - shell_call: [".$shell_call."]\n" if $debug; open (my $file_handle, "<", $shell_call) or die "Failed to read: [".$shell_call."], the error was: ".$!."\n"; while(<$file_handle>) { chomp; $uuid = $_; print __LINE__."; [ Debug ] - uuid: [".$uuid."]\n" if $debug; } close $file_handle; if ($uuid =~ /^(\w+)-/) { $id = $1; print __LINE__."; [ Debug ] - id: [".$id."]\n" if $debug; } } ### NOTE: RAID 0 is not RAID (literally or in this case). So '0' means 'no raid' # If I have 2+ drives of the same size as 'use_drive', I will create a RAID array. my $raid_level = 0; my $hr_size = $device->{$use_drive}{hr_size}; my $count = @{$drives->{by_hr_size}{$hr_size}}; print __LINE__."; [ Debug ] - Drives of size: [".$hr_size."]: [".$count."].\n" if $debug; if ($count == 0) { $raid_level = 0; } elsif ($count == 2) { $raid_level = 1; } elsif ($count == 4) { $raid_level = 10; } elsif (($count == 3) or ($count == 5)) { $raid_level = 5; } elsif ($count > 5) { $raid_level = 6; } my $say_use_drive = $use_drive; if (not $raid_level) { print "Building a standard partition layout for: [".$use_drive."] which is: [".$hr_size."]\n"; } else { print "Building a software RAID level: [".$raid_level."] array using the: [".$count."x] [".$hr_size."] drives;\n"; $say_use_drive = ""; foreach my $path (sort {$a cmp $b} @{$drives->{by_hr_size}{$hr_size}}) { print "- ".$path."\n"; $say_use_drive .= $path.","; } $say_use_drive =~ s/,$//; } ### NOTE: kickstart sizes are in MiB # Prepare some variables my $swap_size = 8192; my $root_size = 0; my $vg_name = $type."_".$id; # If this machine has a small size, we'll cut back the swap and root sizes. my $per_disk_space = sprintf("%.2f", ($device->{$use_drive}{size} /= (2 ** 20))); my $available_space = $per_disk_space; print __LINE__."; [ Debug ] - per_disk_space: [".$per_disk_space." (".hr_size($per_disk_space * (2**20)).")], available_space: [".$available_space." (".hr_size($available_space * (2**20)).")]\n" if $debug; if ($raid_level == 10) { # Total == 2 x single disk $available_space *= 2; print __LINE__."; [ Debug ] - available_space: [".$available_space."]\n" if $debug; } elsif ($raid_level == 5) { # Total == count x Disks - 1 $available_space = ($per_disk_space * $count) - $per_disk_space; print __LINE__."; [ Debug ] - available_space: [".$available_space."]\n" if $debug; } elsif ($raid_level == 6) { # Total == count x Disks - 2 $available_space = ($per_disk_space * $count) - ($per_disk_space * 2); print __LINE__."; [ Debug ] - available_space: [".$available_space."]\n" if $debug; } # Now, how much space is available after taking some for BIOSBOOT and /boot ? $available_space -= 2; print __LINE__."; [ Debug ] - available_space: [".$available_space." (".hr_size($available_space * (2**20)).")]\n" if $debug; if ($available_space < 40960) { # Not enough space for the standard layout. $swap_size = 4096; print __LINE__."; [ Debug ] - swap_size: [".$swap_size."]\n" if $debug; } # The left over space is for '/' (we'll shorten this up to 40GiB for nodes and DR hosts next) $root_size = $available_space - $swap_size; print __LINE__."; [ Debug ] - root_size: [".$root_size."]\n" if $debug; print __LINE__."; [ Debug ] - type: [".$type."], root_size: [".$root_size."]\n" if $debug; if (($type ne "striker") && ($root_size > 40960)) { $root_size = 40960; print __LINE__."; [ Debug ] - root_size: [".$root_size."]\n" if $debug; } # Round down to an event integer. $root_size =~ s/\.\d+$//; print __LINE__."; Assigning: [".hr_size($swap_size * (2**20))." (".$swap_size." MiB)], root_size: [".hr_size($root_size * (2**20))." (".$root_size.") MiB]\n" if $debug; # Build the partition file. my $partition_file = "/tmp/plan_partitions.out"; my $partition_body = "zerombr clearpart --all --drives=".$say_use_drive." ignoredisk --only-use=".$say_use_drive." bootloader --location=mbr --driveorder=".$say_use_drive." --boot-drive=".$use_drive; if (not $raid_level) { # Finally, we've got our output. $partition_body .= " # Partitions part biosboot --fstype=biosboot --size=2 part pv.01 --fstype=lvmpv --size=100 --ondisk=".$use_drive." --grow # LVM Volume groups volgroup ".$vg_name." --pesize=4096 pv.01 # LVM logical volumes logvol swap --fstype=swap --size=".$swap_size." --name=lv_swap --vgname=".$vg_name." logvol / --fstype=xfs --size=100 --grow --maxsize=".$root_size." --name=lv_root --vgname=".$vg_name." "; } else { $partition_body .= " # biosboot "; for (my $i = 0; $i < $count; $i++) { $partition_body .= "part biosboot --fstype=biosboot --size=2 --ondisk=".$drives->{by_hr_size}{$hr_size}->[$i]."\n"; } $partition_body .= " # LVM PV "; my $say_raid = ""; for (my $i = 0; $i < $count; $i++) { my $disk_number = $i + 1; $partition_body .= "part raid.1".$disk_number." --size 100 --grow --ondisk=".$drives->{by_hr_size}{$hr_size}->[$i]."\n"; $say_raid .= "raid.1".$disk_number." "; } $partition_body .= "raid pv.01 --fstype=xfs --device=pv.01 --level=RAID".$raid_level." ".$say_raid." # LVM Volume groups volgroup ".$vg_name." pv.01 # LVM logical volumes logvol swap --fstype=swap --size=".$swap_size." --name=lv_swap --vgname=".$vg_name." logvol / --fstype=xfs --size=100 --grow --maxsize=".$root_size." --name=lv_root --vgname=".$vg_name." "; } # Wipe out the start of each disk so that the install doesn't puke if it sees, for example, an mdadm # signature on the sole disk being used as an install target. foreach my $path (split/,/, $say_use_drive) { print "[ NOTE ] - Wiping the boot sector of: [".$path."] and configuring it for a GPT label.\n"; my $dd_out = system_call("/bin/dd bs=5120 count=1 if=/dev/zero of=".$path." oflag=dsync"); print __LINE__."; [ Debug ] - dd output: ================================================================================ ".$dd_out." ================================================================================\n" if $debug; my $partprobe_out = system_call("/sbin/partprobe --summary ".$path); print __LINE__."; [ Debug ] - partprobe summary. ================================================================================ ".$partprobe_out." ================================================================================\n" if $debug; my $partx_out = system_call("/sbin/partx --update --verbose ".$path); print __LINE__."; [ Debug ] - parted print output showing new layout. ================================================================================ ".$partx_out." ================================================================================\n" if $debug; } # Flush things out. The article says to blindly sleep 30, but it says to do so to make sure udev, partx and # others have updated. We're forcing the issue, which should be faster and safer. system_call("/bin/sync"); system_call("/sbin/udevadm settle"); # Write out the file. print __LINE__."; [ Debug ] - partition_body: ================================================================================ ".$partition_body." ================================================================================\n"; print "Writing out the partition plan to: [".$partition_file."]\n"; # Write it to the temp file that the kickstart's %include will look for. my $shell_call = $partition_file; print __LINE__."; [ Debug ] - shell_call: [".$shell_call."]\n" if $debug; open (my $file_handle, ">", $shell_call) or die "Failed to write: [".$shell_call."], the error was: ".$!."\n"; print $file_handle $partition_body; close $file_handle; print "Completed successfully, exiting.\n"; # We're done. exit(0); ### Functions # Make the size easier to read for users sub hr_size { my ($size) = @_; my $hr_size = $size; if ($size < 1023) { # Bytes $hr_size .= " B"; } elsif ($size < (2 ** 20)) { # Kibibyte $hr_size = sprintf("%.1f", ($size /= (2 ** 10)))." KiB"; } elsif ($size < (2 ** 30)) { # Mebibyte $hr_size = sprintf("%.2f", ($size /= (2 ** 20)))." MiB"; } elsif ($size < (2 ** 40)) { # Gibibyte $hr_size = sprintf("%.2f", ($size /= (2 ** 30)))." GiB"; } elsif ($size < (2 ** 50)) { # Tebibyte $hr_size = sprintf("%.2f", ($size /= (2 ** 40)))." TiB"; } else { # Pebibyte or higher $hr_size = sprintf("%.3f", ($size /= (2 ** 40)))." PiB"; } return($hr_size); } sub system_call { my ($command) = @_; my $output = ""; open (my $file_handle, $command." 2>&1 |") or die "Failed to call: [".$command."], error was: [".$!."]\n"; while (<$file_handle>) { chomp; my $line = $_; $line =~ s/\n$//; $line =~ s/\r$//; $output .= $line."\n"; } close $file_handle; $output =~ s/\n$//s; return($output); } %end # Tradional BIOS based PXE menu # # Notes: # - Use the 'vmlinuz' and 'initrd.img' from the Netinstall ISO. The others don't find the NIC, so can't net # install. # # Use the high-colour menu system. default vesamenu.c32 # Time out and use the default menu option. Defined as tenths of a second. TIMEOUT 6000 # Prompt the user. Set to '1' to automatically choose the default option. This # is really meant for files matched to MAC addresses. PROMPT 0 # Set the boot menu to be 1024x768 with a nice background image. Be careful to # ensure that all your user's can see this resolution! Default is 640x480. MENU RESOLUTION 1024 768 # The background image MENU BACKGROUND splash.jpg MENU TITLE #!string!message_0080!# # Below, the hash (#) character is replaced with the countdown timer. The # '{,s}' allows for pluralizing a word and is used when the value is >= '2'. MENU AUTOBOOT #!string!message_0081!# MENU TABMSG #!string!message_0082!# MENU NOTABMSG #!string!message_0083!# # The following options set the various colours used in the menu. All possible # options are specified except for F# help options. The colour is expressed as # two hex characters between '00' and 'ff' for alpha, red, green and blue # respectively (#AARRGGBB). # Format is: MENU COLOR MENU COLOR screen 0 #00000000 #00000000 std # background colour not covered by the splash image MENU COLOR border 0 #bbd02724 #ee000000 std # The wire-frame border MENU COLOR title 0 #fff2f2f2 #ee000000 std # Menu title text MENU COLOR sel 0 #fff2f2f2 #ee000000 std # Selected menu option MENU COLOR hotsel 0 #fffff2f2 #ee000000 std # The selected hotkey (set with ^ in MENU LABEL) MENU COLOR unsel 0 #ffc2c2c2 #ee000000 std # Unselected menu options MENU COLOR hotkey 0 #ffffc2c2 #ee000000 std # Unselected hotkeys (set with ^ in MENU LABEL) MENU COLOR tabmsg 0 #c0a2a2a2 #00000000 std # Tab text MENU COLOR timeout_msg 0 #ffc2c2c2 #00000000 std # Timout text MENU COLOR timeout 0 #ffffc2c2 #00000000 std # Timout counter MENU COLOR disabled 0 #80515151 #ee000000 std # Disabled menu options, including SEPARATORs MENU COLOR cmdmark 0 #c0444444 #ee000000 std # Command line marker - The '> ' on the left when editing an option MENU COLOR cmdline 0 #c0f2f2f2 #ee000000 std # Command line - The text being edited # Options below haven't been tested, descriptions may be lacking. MENU COLOR scrollbar 0 #407f7f7f #00000000 std # Scroll bar MENU COLOR pwdborder 0 #80d02724 #20ffffff std # Password box wire-frame border MENU COLOR pwdheader 0 #80f7f7f7 #20ffffff std # Password box header MENU COLOR pwdentry 0 #80f2f2f2 #20ffffff std # Password entry field MENU COLOR help 0 #c0f2f2f2 #c0000000 std # Help text, if set via 'TEXT HELP ... ENDTEXT' ### NOTE: inst.repo is the image of the install media, looks for .treeinfo label striker menu label #!string!message_0084!# TEXT HELP #!string!message_0085!# ENDTEXT kernel fedora28/vmlinuz # NOTE: add ' rd.debug' below for debugging append initrd=fedora28/initrd.img root=live:#!variable!base_url!#/os/images/install.img inst.stage2=#!variable!base_url!#/os/ ip=dhcp inst.ks=#!variable!base_url!#/kickstart/striker.ks inst.gpt inst.sshd label node menu label #!string!message_0086!# TEXT HELP #!string!message_0087!# ENDTEXT kernel fedora28/vmlinuz append initrd=fedora28/initrd.img root=live:#!variable!base_url!#/os/images/install.img inst.stage2=#!variable!base_url!#/os/ ip=dhcp inst.ks=#!variable!base_url!#/kickstart/node.ks inst.gpt inst.sshd label node menu label #!string!message_0088!# TEXT HELP #!string!message_0089!# ENDTEXT kernel fedora28/vmlinuz append initrd=fedora28/initrd.img root=live:#!variable!base_url!#/os/images/install.img inst.stage2=#!variable!base_url!#/os/ ip=dhcp inst.ks=#!variable!base_url!#/kickstart/dr.ks inst.gpt inst.sshd label rescue menu label #!string!message_0090!# TEXT HELP #!string!message_0091!# ENDTEXT kernel fedora28/vmlinuz append initrd=fedora28/initrd.img ip=dhcp root=live:#!variable!base_url!#/os/LiveOS/squashfs.img rescue inst.repo=#!variable!base_url!#/os/ ip=dhcp inst.sshd label fedora menu label #!string!message_0092!# TEXT HELP #!string!message_0093!# ENDTEXT kernel fedora28/vmlinuz append initrd=fedora28/initrd.img root=live:#!variable!base_url!#/os/images/install.img inst.repo=#!variable!base_url!#/os/ ip=dhcp inst.gpt inst.sshd label next menu default menu label #!string!message_0094!# localboot 0xffff