use IO::Select;
use Cwd 'abs_path';
use Gtk3 '-init';
-use Gtk3::WebKit;
+use Gtk3::WebKit2;
use Encode;
use String::ShellQuote;
use Data::Dumper;
use File::Basename;
+use File::Path;
use Time::HiRes;
use ProxmoxInstallerSetup;
my $zfspoolname = $opt_testmode ? $zfstestpool : 'rpool';
my $zfsrootvolname = "$setup->{product}-1";
+my $product_fullname = {
+ pve => 'Proxmox VE',
+ pmg => 'Proxmox MailGateway',
+};
+
my $storage_cfg_zfs = <<__EOD__;
dir: local
path /var/lib/vz
my $proxmox_cddir = $opt_testmode ? "../pve-cd-builder/tmp/data-gz/" : "/cdrom";
my $proxmox_pkgdir = "${proxmox_cddir}/proxmox/packages/";
-my $grub_plattform = "pc"; # pc, efi-amd64 or efi-ia32
-
-$grub_plattform = "efi-amd64" if -d "/sys/firmware/efi";
+my $boot_type = -d '/sys/firmware/efi' ? 'efi' : 'bios';
my $IPV4OCTET = "(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9])?[0-9])";
my $IPV4RE = "(?:(?:$IPV4OCTET\\.){3}$IPV4OCTET)";
next if $info !~ m/^E: DEVTYPE=disk$/m;
next if $info =~ m/^E: ID_CDROM/m;
+ next if $info =~ m/^E: ID_FS_TYPE=iso9660/m;
my ($name) = $info =~ m/^N: (\S+)$/m;
if ($dev =~ m|^/dev/sd([a-h]?[a-z]\|i[a-v])$|) {
return "${dev}$partnum";
+ } elsif ($dev =~ m|^/dev/xvd[a-z]$|) {
+ # Citrix Hypervisor blockdev
+ return "${dev}$partnum";
} elsif ($dev =~ m|^/dev/[hxev]d[a-z]$|) {
return "${dev}$partnum";
} elsif ($dev =~ m|^/dev/[^/]+/c\d+d\d+$|) {
next if $part eq $disk;
next if $part !~ /^\Q$disk\E/;
eval { syscmd("pvremove -ff -y $part"); };
+ eval { syscmd("zpool labelclear -f $part"); };
eval { syscmd("dd if=/dev/zero of=$part bs=1M count=16"); };
}
};
return ($os_size, $osdev, $efibootdev);
}
+sub get_pv_list_from_vgname {
+ my ($vgname) = @_;
+
+ my $res;
+
+ my $parser = sub {
+ my $line = shift;
+ $line =~ s/^\s+//;
+ $line =~ s/\s+$//;
+ return if !$line;
+ my ($pv, $vg_uuid) = split(/\s+/, $line);
+
+ if (!defined($res->{$vg_uuid}->{pvs})) {
+ $res->{$vg_uuid}->{pvs} = "$pv";
+ } else {
+ $res->{$vg_uuid}->{pvs} .= ", $pv";
+ }
+ };
+ run_command("pvs --noheadings -o pv_name,vg_uuid -S vg_name='$vgname'", $parser, undef, 1);
+
+ return $res;
+}
+
+sub ask_existing_vg_rename_or_abort {
+ my ($vgname) = @_;
+
+ # this normally only happens if one put a disk with a PVE installation in
+ # this server and that disk is not the installation target.
+ my $duplicate_vgs = get_pv_list_from_vgname($vgname);
+ return if !$duplicate_vgs;
+
+ my $message = "Detected existing '$vgname' Volume Group(s)! Do you want to:\n";
+
+ for my $vg_uuid (keys %$duplicate_vgs) {
+ my $vg = $duplicate_vgs->{$vg_uuid};
+
+ # no high randomnes properties, but this is only for the cases where
+ # we either have multiple "$vgname" vgs from multiple old PVE disks, or
+ # we have a disk with both a "$vgname" and "$vgname-old"...
+ my $short_uid = sprintf "%08X", rand(0xffffffff);
+ $vg->{new_vgname} = "$vgname-OLD-$short_uid";
+
+ $message .= "rename VG backed by PV '$vg->{pvs}' to '$vg->{new_vgname}'\n";
+ }
+ $message .= "or cancel the installation?";
+
+ my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'question', 'ok-cancel', $message);
+ my $response = $dialog->run();
+ $dialog->destroy();
+
+ if ($response eq 'ok') {
+ for my $vg_uuid (keys %$duplicate_vgs) {
+ my $vg = $duplicate_vgs->{$vg_uuid};
+ my $new_vgname = $vg->{new_vgname};
+
+ syscmd("vgrename $vg_uuid $new_vgname") == 0 ||
+ die "could not rename VG from '$vg->{pvs}' ($vg_uuid) to '$new_vgname'!\n";
+ }
+ } else {
+ set_next("_Reboot", sub { exit (0); } );
+ display_html("fail.htm");
+ die "Cancled installation by user, due to already existing volume group '$vgname'\n";
+ }
+}
+
sub create_lvm_volumes {
my ($lvmdev, $os_size, $swap_size) = @_;
my $vgname = $setup->{product};
+ ask_existing_vg_rename_or_abort($vgname);
+
my $rootdev = "/dev/$vgname/root";
my $datadev = "/dev/$vgname/data";
my $swapfile;
return $swapsize;
}
+sub prepare_systemd_boot_esp {
+ my ($espdev, $targetdir) = @_;
+
+ my $espuuid = find_dev_by_uuid($espdev);
+ my $espmp = "var/tmp/$espuuid";
+ mkdir "$targetdir/$espmp";
+
+ syscmd("mount -n $espdev -t vfat $targetdir/$espmp") == 0 ||
+ die "unable to mount ESP $espdev\n";
+
+ File::Path::make_path("$targetdir/$espmp/EFI/proxmox") ||
+ die "unable to create directory $targetdir/$espmp/EFI/proxmox\n";
+
+ syscmd("chroot $targetdir bootctl --path /$espmp install") == 0 ||
+ die "unable to install systemd-boot loader\n";
+ write_config("timeout 3\ndefault proxmox-*\n",
+ "$targetdir/$espmp/loader/loader.conf");
+
+ syscmd("umount $targetdir/$espmp") == 0 ||
+ die "unable to umount ESP $targetdir/$espmp\n";
+
+}
+
+sub prepare_grub_efi_boot_esp {
+ my ($dev, $espdev, $targetdir) = @_;
+
+ syscmd("mount -n $espdev -t vfat $targetdir/boot/efi") == 0 ||
+ die "unable to mount $espdev\n";
+
+ my $rc = syscmd("chroot $targetdir /usr/sbin/grub-install --target x86_64-efi --no-floppy --bootloader-id='proxmox' $dev");
+ if ($rc != 0) {
+ if ($boot_type eq 'efi') {
+ die "unable to install the EFI boot loader on '$dev'\n";
+ } else {
+ warn "unable to install the EFI boot loader on '$dev', ignoring (not booted using UEFI)\n";
+ }
+ }
+ # also install fallback boot file (OVMF does not boot without)
+ mkdir("$targetdir/boot/efi/EFI/BOOT");
+ syscmd("cp $targetdir/boot/efi/EFI/proxmox/grubx64.efi $targetdir/boot/efi/EFI/BOOT/BOOTx64.EFI") == 0 ||
+ die "unable to copy efi boot loader\n";
+
+ syscmd("umount $targetdir/boot/efi") == 0 ||
+ die "unable to umount $targetdir/boot/efi\n";
+}
sub extract_data {
my ($basefile, $targetdir) = @_;
my ($devlist, $bootdevlist, $vdev) = get_zfs_raid_setup();
- my $disksize;
foreach my $hd (@$devlist) {
&$clean_disk(@$hd[1]);
}
+
+ my $disksize;
foreach my $hd (@$bootdevlist) {
my $devname = @$hd[1];
- my ($size, $osdev) =
+ my ($size, $osdev, $efidev) =
partition_bootable_disk($devname, $config_options->{hdsize}, 'BF01');
+
zfs_mirror_size_check($disksize, $size) if $disksize;
- push @$bootdevinfo, { devname => $devname, osdev => $osdev};
+
+ push @$bootdevinfo, {
+ esp => $efidev,
+ devname => $devname,
+ osdev => $osdev
+ };
$disksize = $size;
}
my $devname = $di->{devname};
$di->{by_id} = find_stable_path ("/dev/disk/by-id", $devname);
- # Note: using /dev/disk/by-id/ does not work for unknown reason, we get
- # cannot create 'rpool': no such pool or dataset
- #my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
+ my $osdev = find_stable_path ("/dev/disk/by-id", $di->{osdev}) || $di->{osdev};
- my $osdev = $di->{osdev};
$vdev =~ s/ $devname/ $osdev/;
}
}
}
+ mkdir "$targetdir/mnt";
+ mkdir "$targetdir/mnt/hostrun";
+ syscmd("mount --bind /run $targetdir/mnt/hostrun") == 0 ||
+ die "unable to bindmount run on $targetdir/mnt/hostrun\n";
+
update_progress(1, 0.05, $maxper, "extracting base system");
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size) = stat ($basefile);
die "unable to mount proc on $targetdir/proc\n";
syscmd("mount -n -t sysfs sysfs $targetdir/sys") == 0 ||
die "unable to mount sysfs on $targetdir/sys\n";
+ if ($boot_type eq 'efi') {
+ syscmd("mount -n -t efivarfs efivarfs $targetdir/sys/firmware/efi/efivars") == 0 ||
+ die "unable to mount efivarfs on $targetdir/sys/firmware/efi/efivars: $!\n";
+ }
+ syscmd("chroot $targetdir mount --bind /mnt/hostrun /run") == 0 ||
+ die "unable to re-bindmount hostrun on /run in chroot\n";
update_progress(1, $maxper, 0.5, "configuring base system");
# Note: this is required by current grub, but really dangerous, because
# vfat does not have journaling, so it triggers manual fsck after each crash
# so we only mount /boot/efi if really required (efi systems).
- if ($grub_plattform =~ m/^efi-/) {
+ if ($boot_type eq 'efi' && !$use_zfs) {
if (scalar(@$bootdevinfo)) {
my $di = @$bootdevinfo[0]; # simply use first disk
+
if ($di->{esp}) {
my $efi_boot_uuid = $di->{esp};
if (my $uuid = find_dev_by_uuid ($di->{esp})) {
diversion_add($targetdir, "/usr/sbin/update-grub", "/bin/true");
diversion_add($targetdir, "/usr/sbin/update-initramfs", "/bin/true");
+ my $machine_id = run_command("systemd-id128 new");
+ die "unable to create a new machine-id\n" if ! $machine_id;
+ write_config($machine_id, "$targetdir/etc/machine-id");
+
+ syscmd("cp /etc/hostid $targetdir/etc/") == 0 ||
+ die "unable to copy hostid\n";
+
syscmd("touch $targetdir/proxmox_install_mode");
my $grub_install_devices_txt = '';
chomp;
my $path = $_;
my ($deb) = $path =~ m/${proxmox_pkgdir}\/(.*\.deb)/;
-# if ($deb =~ m/^grub-efi-/ && $deb !~ m/^grub-${grub_plattform}/) {
-# $count++;
-# next;
-# }
update_progress($count/$pkg_count, 0.5, 0.75, "extracting $deb");
print "extracting: $deb\n";
syscmd("cp $path $targetdir/tmp/$deb") == 0 ||
syscmd("sed -i -e 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"root=ZFS=$zfspoolname\\/ROOT\\/$zfsrootvolname boot=zfs\"/' $targetdir/etc/default/grub") == 0 ||
die "unable to update /etc/default/grub\n";
+ if ($boot_type eq 'efi') {
+ write_config("root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs", "$targetdir/etc/kernel/cmdline");
+ }
+
}
diversion_remove($targetdir, "/usr/sbin/update-grub");
syscmd("chroot $targetdir /usr/sbin/grub-install --target i386-pc --no-floppy --bootloader-id='proxmox' $dev") == 0 ||
die "unable to install the i386-pc boot loader on '$dev'\n";
- if ($di->{esp}) {
- syscmd("mount -n $di->{esp} -t vfat $targetdir/boot/efi") == 0 ||
- die "unable to mount $di->{esp}\n";
- my $rc = syscmd("chroot $targetdir /usr/sbin/grub-install --target x86_64-efi --no-floppy --bootloader-id='proxmox' $dev");
- if ($rc != 0) {
- if (-d '/sys/firmware/efi') {
- die "unable to install the EFI boot loader on '$dev'\n";
- } else {
- warn "unable to install the EFI boot loader on '$dev', ignoring (not booted using UEFI)\n";
- }
+ if (my $esp = $di->{esp}) {
+ if ($use_zfs) {
+ prepare_systemd_boot_esp($esp, $targetdir);
+ } else {
+ prepare_grub_efi_boot_esp($dev, $esp, $targetdir);
}
- # also install fallback boot file (OVMF does not boot without)
- mkdir("$targetdir/boot/efi/EFI/BOOT");
- syscmd("cp $targetdir/boot/efi/EFI/proxmox/grubx64.efi $targetdir/boot/efi/EFI/BOOT/BOOTx64.EFI") == 0 ||
- die "unable to copy efi boot loader\n";
-
- syscmd("umount $targetdir/boot/efi") == 0 ||
- die "unable to umount $targetdir/boot/efi\n";
}
}
syscmd("chroot $targetdir /usr/sbin/update-grub") == 0 ||
die "unable to update boot loader config\n";
+ if ($use_zfs && $boot_type eq 'efi') {
+ syscmd("chroot $targetdir /etc/kernel/postinst.d/zz-pve-efiboot") == 0 ||
+ die "unable to generate systemd-boot config\n";
+ }
+
syscmd("umount $targetdir/dev");
}
# cleanup
- # hack: remove dead.letter from sshd installation
- syscmd("rm -rf $targetdir/dead.letter");
-
unlink "$targetdir/usr/sbin/policy-rc.d";
diversion_remove($targetdir, "/sbin/start-stop-daemon");
syscmd("chroot $targetdir /usr/bin/dpkg-query -W --showformat='\${package}\n'> final.pkglist");
}
+ syscmd("umount $targetdir/run");
+ syscmd("umount $targetdir/mnt/hostrun");
syscmd("umount $targetdir/tmp");
syscmd("umount $targetdir/proc");
+ syscmd("umount $targetdir/sys/firmware/efi/efivars");
syscmd("umount $targetdir/sys");
if ($use_zfs) {
my $data = file_get_contents($path);
if ($filename eq 'license.htm') {
- my $license = decode('utf8', file_get_contents("${proxmox_cddir}/EULA"));
+ my $license = eval { decode('utf8', file_get_contents("${proxmox_cddir}/EULA")) };
+ if (my $err = $@) {
+ die $err if !$opt_testmode;
+ $license = "TESTMODE: Ignore non existent EULA...\n";
+ }
my $title = "END USER LICENSE AGREEMENT (EULA)";
$data =~ s/__LICENSE__/$license/;
$data =~ s/__LICENSE_TITLE__/$title/;
}
- $htmlview->load_html_string($data, $url);
+ $htmlview->load_html($data, $url);
$last_display_change = time();
}
my $vbox2 = Gtk3::VBox->new(0, 0);
$hbox->add($vbox2);
- $htmlview = Gtk3::WebKit::WebView->new();
+ $htmlview = Gtk3::WebKit2::WebView->new();
my $scrolls = Gtk3::ScrolledWindow->new();
$scrolls->add($htmlview);
my $device_change_handler = sub {
my $current = shift;
- $ipconf->{selected} = $device_active_map->{$current->get_active()};
+
+ my $new = $device_active_map->{$current->get_active()};
+ return if defined($ipconf->{selected}) && $new eq $ipconf->{selected};
+
+ $ipconf->{selected} = $new;
my $iface = $ipconf->{ifaces}->{$ipconf->{selected}};
$config->{mngmt_nic} = $iface->{name};
$ipconf_entry_addr->set_text($iface->{inet}->{addr} || $iface->{inet6}->{addr})
my $html_data = file_get_contents($ack_template);
my %config_values = (
- __target_hd__ => $target_hd,
+ __target_hd__ => join(' | ', @{$config_options->{target_hds}}),
__target_fs__ => $config_options->{filesys},
__country__ => $cmap->{country}->{$country}->{name},
__timezone__ => $timezone,
my $target_hd_combo;
my $target_hd_label;
-my $hdopion_first_setup = 1;
+my $hdoption_first_setup = 1;
my $create_basic_grid = sub {
my $grid = Gtk3::Grid->new();
});
}
- if ($hdopion_first_setup) {
+ if ($hdoption_first_setup) {
$disk_selector->set_active ($i+1) if $hds->[$i];
} else {
my $hdind = 0;
$grid->attach($sep, 0, $row, 2, 1);
$row++;
+ my $hw_raid_note = Gtk3::Label->new("Note: ZFS is not compatible with disks backed by a hardware RAID controller. For details see the reference documentation.");
+ $hw_raid_note->set_line_wrap(1);
+ $hw_raid_note->set_max_width_chars(30);
+ $hw_raid_note->set_visible(0);
+ $grid->attach($hw_raid_note, 0, $row++, 2, 1);
+
my $hdsize_labeled_widgets = [];
# size compute
$grid->attach($options_stack, 0, $row, 2, 1);
$row++;
- $hdopion_first_setup = 0;
+ $hdoption_first_setup = 0;
my $switch_view = sub {
my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
$target_hd_combo->set_visible(!$raid);
$options_stack->get_child_by_name("hdsize")->set_visible(!$raid);
$options_stack->get_child_by_name("raiddisk")->set_visible($raid);
+ $hw_raid_note->set_visible($raid);
$options_stack_switcher->set_visible($enable_zfs_opts);
$options_stack->get_child_by_name("raidzfsadvanced")->set_visible($enable_zfs_opts);
if ($raid) {
&$switch_view();
});
+ my $sep2 = Gtk3::HSeparator->new();
+ $sep2->set_visible(1);
+ $contarea->pack_end($sep2, 1, 1, 10);
+
$dialog->show();
$dialog->run();
return ($devlist, $mode);
}
+my $last_hd_selected = 0;
sub create_hdsel_view {
$prev_btn->set_sensitive(1); # enable previous button at this point
$vbox->pack_start($hbox, 0, 0, 10);
my ($disk, $devname, $size, $model) = @{@$hds[0]};
- $target_hd = $devname;
+ $target_hd = $devname if !defined($target_hd);
$target_hd_label = Gtk3::Label->new("Target Harddisk: ");
$hbox->pack_start($target_hd_label, 0, 0, 0);
$target_hd_combo->append_text (get_device_desc($devname, $size, $model));
}
- $target_hd_combo->set_active(0);
+ my $raid = $config_options->{filesys} =~ m/zfs|btrfs/;
+ if ($raid) {
+ $target_hd_label->set_text("Target: $config_options->{filesys} ");
+ $target_hd_combo->set_visible(0);
+ $target_hd_combo->set_no_show_all(1);
+ }
+ $target_hd_combo->set_active($last_hd_selected);
$target_hd_combo->signal_connect(changed => sub {
$a = shift->get_active;
my ($disk, $devname) = @{@$hds[$a]};
+ $last_hd_selected = $a;
$target_hd = $devname;
});
set_next(undef, sub {
if ($config_options->{filesys} =~ m/zfs/) {
- eval { get_zfs_raid_setup(); };
+ my ($devlist) = eval { get_zfs_raid_setup() };
if (my $err = $@) {
- display_message("Warning: $err\n" .
- "Please fix ZFS setup first.");
- } else {
- $step_number++;
- create_country_view();
+ display_message("Warning: $err\nPlease fix ZFS setup first.");
+ return;
}
+ $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
} elsif ($config_options->{filesys} =~ m/btrfs/) {
- eval { get_btrfs_raid_setup(); };
+ my ($devlist) = eval { get_btrfs_raid_setup() };
if (my $err = $@) {
- display_message("Warning: $err\n" .
- "Please fix BTRFS setup first.");
- } else {
- $step_number++;
- create_country_view();
+ display_message("Warning: $err\nPlease fix BTRFS setup first.");
+ return;
}
+ $config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
} else {
- $step_number++;
- create_country_view();
+ $config_options->{target_hds} = [ $target_hd ];
}
+
+ $step_number++;
+ create_country_view();
});
}
cleanup_view();
+ if (int($total_memory) < 1024) {
+ my $fullname = $product_fullname->{$setup->{product}};
+
+ display_error("Less than 1 GiB of usable memory detected, installation will probably fail.\n\n".
+ "See 'System Requirements' in the $fullname documentation.");
+ }
+
if ($setup->{product} eq 'pve') {
eval {
my $cpuinfo = file_get_contents('/proc/cpuinfo');
if ($cpuinfo && !($cpuinfo =~ /^flags\s*:.*(vmx|svm)/m)) {
- display_error("No support for KVM virtualisation detected.\n\n" .
+ display_error("No support for KVM virtualization detected.\n\n" .
"Check BIOS settings for Intel VT / AMD-V / SVM.")
}
};