my $zfspoolname = $opt_testmode ? $zfstestpool : 'rpool';
my $zfsrootvolname = "$setup->{product}-1";
+my $product_fullname = {
+ pve => 'Proxmox VE',
+ pmg => 'Proxmox Mail Gateway',
+ pbs => 'Proxmox Backup Server',
+};
+
my $storage_cfg_zfs = <<__EOD__;
dir: local
path /var/lib/vz
inet_interfaces = loopback-only
recipient_delimiter = +
+compatibility_level = 2
+
_EOD
sub shellquote {
my @disks = split /,/, $opt_testmode;
for my $disk (@disks) {
- push @$res, [-1, $disk, int((-s $disk)/512), "TESTDISK"];
+ push @$res, [-1, $disk, int((-s $disk)/512), "TESTDISK", 512];
}
return $res;
}
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 (length ($model) > 30) {
$model = substr ($model, 0, 30);
}
- push @$res, [$count++, $real_name, $size, $model] if $size;
+
+ my $logical_bsize = file_read_firstline("$bd/queue/logical_block_size") // '';
+ chomp $logical_bsize;
+ $logical_bsize = undef if !($logical_bsize && $logical_bsize =~ m/^\d+$/);
+
+ push @$res, [$count++, $real_name, $size, $model, $logical_bsize] if $size;
} else {
print STDERR "ERROR: unable to map device $dev ($bd)\n";
}
sub read_cmap {
my $countryfn = "${proxmox_libdir}/country.dat";
- open (TMP, "<$countryfn") || die "unable to open '$countryfn' - $!\n";
+ open (TMP, "<:encoding(utf8)", "$countryfn") || die "unable to open '$countryfn' - $!\n";
my $line;
my $country = {};
my $countryhash = {};
my ($dev) = @_;
foreach my $hd (@$hds) {
- my ($disk, $devname, $size, $model) = @$hd;
- # size is always in 512B "sectors"! convert to KB
+ my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
+ # size is always (also for 4kn disks) in 512B "sectors"! convert to KB
return int($size/2) if $devname eq $dev;
}
die "no such device '$dev'\n";
}
+sub logical_blocksize {
+ my ($dev) = @_;
+
+ foreach my $hd (@$hds) {
+ my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
+ return $logical_bsize if $devname eq $dev;
+ }
+
+ die "no such device '$dev'\n";
+}
+
sub get_partition_dev {
my ($dev, $partnum) = @_;
die "unknown partition type '$ptype'"
if !($ptype eq '8E00' || $ptype eq '8300' || $ptype eq 'BF01');
- syscmd("sgdisk -Z ${target_dev}");
my $hdsize = hd_size($target_dev); # size in KB (1024 bytes)
my $restricted_hdsize_mb = 0; # 0 ==> end of partition
my $hdgb = int($hdsize/(1024*1024));
die "hardisk '$target_dev' too small (${hdgb}GB)\n" if $hdgb < 8;
+ syscmd("sgdisk -Z ${target_dev}");
+
# 1 - BIOS boot partition (Grub Stage2): first free 1M
# 2 - EFI ESP: next free 512M
# 3 - OS/Data partition: rest, up to $maxhdsize in MB
syscmd($pcmd) == 0 ||
die "unable to partition harddisk '${target_dev}'\n";
- $pnum = 1;
- $pcmd = ['sgdisk', '-a1', "-n$pnum:34:2047", "-t$pnum:EF02" , $target_dev];
+ my $blocksize = logical_blocksize($target_dev);
- syscmd($pcmd) == 0 ||
- die "unable to create bios_boot partition '${target_dev}'\n";
+ if ($blocksize != 4096) {
+ $pnum = 1;
+ $pcmd = ['sgdisk', '-a1', "-n$pnum:34:2047", "-t$pnum:EF02" , $target_dev];
+
+ syscmd($pcmd) == 0 ||
+ die "unable to create bios_boot partition '${target_dev}'\n";
+ }
&$udevadm_trigger_block();
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;
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 --no-variables --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";
-
+ syscmd("chroot $targetdir pve-efiboot-tool init $espdev") == 0 ||
+ die "unable to init ESP and install systemd-boot loader on '$espdev'\n";
}
sub prepare_grub_efi_boot_esp {
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";
+ eval {
+ 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";
+ # 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";
+ };
+ my $err = $@;
+
+ eval {
+ syscmd("umount $targetdir/boot/efi") == 0 ||
+ die "unable to umount $targetdir/boot/efi\n";
+ };
+ warn $@ if $@;
- syscmd("umount $targetdir/boot/efi") == 0 ||
- die "unable to umount $targetdir/boot/efi\n";
+ die "failed to prepare EFI boot using Grub on '$espdev': $err" if $err;
}
sub extract_data {
die "unable to load zfs kernel module\n" if !$i;
}
+ my $bootloader_err;
+
eval {
my $disksize;
foreach my $hd (@$devlist) {
my $devname = @$hd[1];
+ my $logical_bsize = @$hd[4];
+
&$clean_disk($devname);
my ($size, $osdev, $efidev) =
partition_bootable_disk($devname, undef, '8300');
$rootdev = $osdev if !defined($rootdev); # simply point to first disk
my $by_id = find_stable_path("/dev/disk/by-id", $devname);
- push @$bootdevinfo, { esp => $efidev, devname => $devname,
- osdev => $osdev, by_id => $by_id };
+ push @$bootdevinfo, {
+ esp => $efidev,
+ devname => $devname,
+ osdev => $osdev,
+ by_id => $by_id,
+ logical_bsize => $logical_bsize,
+ };
push @$btrfs_partitions, $osdev;
$disksize = $size;
}
my $disksize;
foreach my $hd (@$bootdevlist) {
my $devname = @$hd[1];
+ my $logical_bsize = @$hd[4];
my ($size, $osdev, $efidev) =
partition_bootable_disk($devname, $config_options->{hdsize}, 'BF01');
push @$bootdevinfo, {
esp => $efidev,
devname => $devname,
- osdev => $osdev
+ osdev => $osdev,
+ logical_bsize => $logical_bsize,
};
$disksize = $size;
}
$vdev =~ s/ $devname/ $osdev/;
}
+ foreach my $hd (@$devlist) {
+ my $devname = @$hd[1];
+ my $by_id = find_stable_path ("/dev/disk/by-id", $devname);
+
+ $vdev =~ s/ $devname/ $by_id/ if $by_id;
+ }
+
zfs_create_rpool($vdev);
} else {
&$clean_disk($target_hd);
+ my $logical_bsize = logical_blocksize($target_hd);
+
my ($os_size, $osdev, $efidev);
($os_size, $osdev, $efidev) =
partition_bootable_disk($target_hd, $config_options->{hdsize}, '8E00');
&$udevadm_trigger_block();
my $by_id = find_stable_path ("/dev/disk/by-id", $target_hd);
- push @$bootdevinfo, { esp => $efidev, devname => $target_hd,
- osdev => $osdev, by_id => $by_id };
+ push @$bootdevinfo, {
+ esp => $efidev,
+ devname => $target_hd,
+ osdev => $osdev,
+ by_id => $by_id,
+ logical_bsize => $logical_bsize,
+ };
my $swap_size = compute_swapsize($os_size);
($rootdev, $swapfile, $datadev) =
foreach my $di (@$bootdevinfo) {
next if !$di->{esp};
- syscmd("mkfs.vfat -F32 $di->{esp}") == 0 ||
+ # FIXME remove '-s1' once https://github.com/dosfstools/dosfstools/issues/111 is fixed
+ my $vfat_extra_opts = ($di->{logical_bsize} == 4096) ? '-s1' : '';
+ syscmd("mkfs.vfat $vfat_extra_opts -F32 $di->{esp}") == 0 ||
die "unable to initialize EFI ESP on device $di->{esp}\n";
}
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";
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 = '';
syscmd("chroot $targetdir /usr/sbin/postfix check");
# cleanup mail queue
syscmd("chroot $targetdir /usr/sbin/postsuper -d ALL");
+ # create /etc/aliases.db (/etc/aliases is shipped in the base squashfs)
+ syscmd("chroot $targetdir /usr/bin/newaliases");
# enable NTP (timedatectl set-ntp true does not work without DBUS)
syscmd("chroot $targetdir /bin/systemctl enable systemd-timesyncd.service");
symlink ("/proc/mounts", "$targetdir/etc/mtab");
syscmd("mount -n --bind /dev $targetdir/dev");
- syscmd("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
- die "unable to install initramfs\n";
+ my $bootloader_err_list = [];
+ eval {
+ syscmd("chroot $targetdir /usr/sbin/update-initramfs -c -k $kapi") == 0 ||
+ die "unable to install initramfs\n";
- foreach my $di (@$bootdevinfo) {
- my $dev = $di->{devname};
- 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 (my $esp = $di->{esp}) {
- if ($use_zfs) {
- prepare_systemd_boot_esp($esp, $targetdir);
- } else {
- prepare_grub_efi_boot_esp($dev, $esp, $targetdir);
+ my $native_4k_disk_bootable = 0;
+ foreach my $di (@$bootdevinfo) {
+ $native_4k_disk_bootable |= ($di->{logical_bsize} == 4096);
+ }
+
+ foreach my $di (@$bootdevinfo) {
+ my $dev = $di->{devname};
+ if (!$native_4k_disk_bootable) {
+ eval {
+ 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";
+ };
+ push @$bootloader_err_list, $@ if $@;
}
+
+ eval {
+ if (my $esp = $di->{esp}) {
+ if ($use_zfs) {
+ prepare_systemd_boot_esp($esp, $targetdir);
+ } else {
+ prepare_grub_efi_boot_esp($dev, $esp, $targetdir);
+ }
+ }
+ };
+ push @$bootloader_err_list, $@ if $@;
}
- }
- syscmd("chroot $targetdir /usr/sbin/update-grub") == 0 ||
- die "unable to update boot loader config\n";
+ syscmd("chroot $targetdir /usr/sbin/update-grub") == 0 ||
+ die "unable to update boot loader config\n";
+ };
+ push @$bootloader_err_list, $@ if $@;
- 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";
+ if (scalar(@$bootloader_err_list) > 0) {
+ $bootloader_err = "bootloader setup errors:\n";
+ map { $bootloader_err .= "- $_" } @$bootloader_err_list;
+ warn $bootloader_err;
}
syscmd("umount $targetdir/dev");
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) {
syscmd("zpool export $zfspoolname");
}
+ if ($bootloader_err) {
+ $err = $err ? "$err\n$bootloader_err" : $bootloader_err;
+ }
+
die $err if $err;
}
my $title = "END USER LICENSE AGREEMENT (EULA)";
$data =~ s/__LICENSE__/$license/;
$data =~ s/__LICENSE_TITLE__/$title/;
+ } elsif ($filename eq 'success.htm') {
+ my $addr = $ipversion == 6 ? "[${ipaddress}]" : "$ipaddress";
+ $data =~ s/\@IPADDR\@/$addr/;
}
$htmlview->load_html($data, $url);
}
my $t3 = $eme->get_text;
- if ($t3 !~ m/^\S+\@\S+\.\S+$/) {
+ if ($t3 !~ m/^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$/) {
display_message("E-Mail does not look like a valid address" .
" (user\@domain.tld)");
$eme->grab_focus();
$disk_selector->set_active(0);
$disk_selector->set_visible(1);
foreach my $hd (@$hds) {
- my ($disk, $devname, $size, $model) = @$hd;
+ my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
$disk_selector->append_text(get_device_desc ($devname, $size, $model));
$disk_selector->{pve_disk_id} = $i;
$disk_selector->signal_connect (changed => sub {
my $devlist = [];
for (my $i = 0; $i < @$hds; $i++) {
if (my $hd = $config_options->{"disksel$i"}) {
- my ($disk, $devname, $size, $model) = @$hd;
+ my ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
die "device '$devname' is used more than once\n"
if $dev_name_hash->{$devname};
$dev_name_hash->{$devname} = $hd;
if abs($expected - $actual) > $expected / 10;
}
+sub legacy_bios_4k_check {
+ my ($lbs) = @_;
+ die "Booting from 4kn drive in legacy BIOS mode is not supported.\n"
+ if (($boot_type ne 'efi') && ($lbs == 4096));
+}
+
sub get_zfs_raid_setup {
my $filesys = $config_options->{filesys};
if ($filesys eq 'zfs (RAID0)') {
push @$bootdevlist, @$devlist[0];
foreach my $hd (@$devlist) {
+ legacy_bios_4k_check(@$hd[4]);
$cmd .= " @$hd[1]";
}
} elsif ($filesys eq 'zfs (RAID1)') {
my $expected_size = @$hd[2]; # all disks need approximately same size
foreach $hd (@$devlist) {
zfs_mirror_size_check($expected_size, @$hd[2]);
+ legacy_bios_4k_check(@$hd[4]);
$cmd .= " @$hd[1]";
push @$bootdevlist, $hd;
}
my $hd1 = @$devlist[$i];
my $hd2 = @$devlist[$i+1];
zfs_mirror_size_check(@$hd1[2], @$hd2[2]); # pairs need approximately same size
+ legacy_bios_4k_check(@$hd1[4]);
+ legacy_bios_4k_check(@$hd2[4]);
$cmd .= ' mirror ' . @$hd1[1] . ' ' . @$hd2[1];
}
$cmd .= " raidz$level";
foreach $hd (@$devlist) {
zfs_mirror_size_check($expected_size, @$hd[2]);
+ legacy_bios_4k_check(@$hd[4]);
$cmd .= " @$hd[1]";
push @$bootdevlist, $hd;
}
my $hbox = Gtk3::HBox->new(0, 0);
$vbox->pack_start($hbox, 0, 0, 10);
- my ($disk, $devname, $size, $model) = @{@$hds[0]};
+ my ($disk, $devname, $size, $model, $logical_bsize) = @{@$hds[0]};
$target_hd = $devname if !defined($target_hd);
$target_hd_label = Gtk3::Label->new("Target Harddisk: ");
$target_hd_combo = Gtk3::ComboBoxText->new();
foreach my $hd (@$hds) {
- ($disk, $devname, $size, $model) = @$hd;
+ ($disk, $devname, $size, $model, $logical_bsize) = @$hd;
$target_hd_combo->append_text (get_device_desc($devname, $size, $model));
}
}
$config_options->{target_hds} = [ map { $_->[1] } @$devlist ];
} else {
+ eval { legacy_bios_4k_check(logical_blocksize($target_hd)) };
+ if (my $err = $@) {
+ display_message("Warning: $err\n");
+ return;
+ }
$config_options->{target_hds} = [ $target_hd ];
}
cleanup_view();
- if (int($total_memory/1024) < 1) {
- display_error("you need at least 1GB memory to install Proxmox\n");
- die "not enough memory";
+ 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') {