#!/usr/bin/perl
-$ENV{DEBIAN_FRONTEND} = 'noninteractive';
-$ENV{LC_ALL} = 'C';
-
use strict;
use warnings;
+$ENV{DEBIAN_FRONTEND} = 'noninteractive';
+$ENV{LC_ALL} = 'C';
+
use Getopt::Long;
use IPC::Open2;
use IPC::Open3;
use IO::File;
use IO::Select;
use Cwd 'abs_path';
+use Glib;
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 POSIX ":sys_wait_h";
use ProxmoxInstallerSetup;
-my $setup = ProxmoxInstallerSetup::setup();
-
-my $opt_testmode;
-
if (!$ENV{G_SLICE} || $ENV{G_SLICE} ne "always-malloc") {
die "do not use slice allocator (run with 'G_SLICE=always-malloc ./proxinstall ...')\n";
}
+my $opt_testmode;
if (!GetOptions('testmode=s' => \$opt_testmode)) {
die "usage error\n";
exit (-1);
}
+my ($setup, $cd_info) = ProxmoxInstallerSetup::setup();
+
my $zfstestpool = "test_rpool";
my $zfspoolname = $opt_testmode ? $zfstestpool : 'rpool';
my $zfsrootvolname = "$setup->{product}-1";
my $logfd = IO::File->new(">/tmp/install.log");
-my $proxmox_libdir = $opt_testmode ?
- Cwd::cwd() . "/testdir/var/lib/pve-installer" : "/var/lib/pve-installer";
+my $proxmox_libdir = $opt_testmode
+ ? Cwd::cwd() . "/testdir/var/lib/proxmox-installer"
+ : "/var/lib/proxmox-installer"
+ ;
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)";
my ($next, $next_fctn, $target_hd);
my ($progress, $progress_status);
-my ($ipversion, $ipaddress, $ipconf_entry_addr);
+my ($ipversion, $ipaddress, $cidr, $ipconf_entry_addr);
my ($netmask, $ipconf_entry_mask);
my ($gateway, $ipconf_entry_gw);
my ($dnsserver, $ipconf_entry_dns);
my $password;
my $mailto = 'mail@example.invalid';
my $cmap;
+my $autoreboot_seconds = 5;
my $config = {
# TODO: add all the user-provided options for previous button
# parse command line args
-my $config_options = {};
+my $config_options = {
+ autoreboot => 1,
+};
-if ($cmdline =~ m/\s(ext3|ext4|xfs)(\s.*)?$/) {
+if ($cmdline =~ m/\s(ext4|xfs)(\s.*)?$/) {
$config_options->{filesys} = $1;
} else {
$config_options->{filesys} = 'ext4';
inet_interfaces = loopback-only
recipient_delimiter = +
+compatibility_level = 2
+
_EOD
sub shellquote {
print $writer $input if defined $input;
close $writer;
- my $select = new IO::Select;
+ my $select = IO::Select->new();
$select->add($reader);
$select->add($error);
return $ostream;
}
+# forks and runs the provided coderef in the child
+# do not use syscmd or run_command as both confuse the GTK mainloop if
+# run from a child process
+sub run_in_background {
+ my ($cmd) = @_;
+
+ my $pid = fork() // die "fork failed: $!\n";
+ if (!$pid) {
+ eval { $cmd->(); };
+ if (my $err = $@) {
+ warn "run_in_background error: $err\n";
+ POSIX::_exit(1);
+ }
+ POSIX::_exit(0);
+ }
+}
+
sub detect_country {
print "trying to detect country...\n";
sub get_memtotal {
- open (MEMINFO, "/proc/meminfo");
+ open (my $MEMINFO, '<', '/proc/meminfo');
my $res = 512; # default to 512 if something goes wrong
- while (my $line = <MEMINFO>) {
+ while (my $line = <$MEMINFO>) {
if ($line =~ m/^MemTotal:\s+(\d+)\s*kB/i) {
$res = int ($1 / 1024);
}
}
- close (MEMINFO);
+ close($MEMINFO);
return $res;
}
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 (my $TMP, "<:encoding(utf8)", "$countryfn") || die "unable to open '$countryfn' - $!\n";
my $line;
my $country = {};
my $countryhash = {};
my $kmap = {};
my $kmaphash = {};
- while (defined ($line = <TMP>)) {
+ while (defined ($line = <$TMP>)) {
if ($line =~ m|^map:([^\s:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]*):$|) {
$kmap->{$1} = {
name => $2,
warn "unable to parse 'country.dat' line: $line";
}
}
- close (TMP);
+ close ($TMP);
+ $TMP = undef;
my $zones = {};
my $cczones = {};
my $zonefn = "/usr/share/zoneinfo/zone.tab";
- open (TMP, "<$zonefn") || die "unable to open '$zonefn' - $!\n";
- while (defined ($line = <TMP>)) {
+ open ($TMP, '<', "$zonefn") || die "unable to open '$zonefn' - $!\n";
+ while (defined ($line = <$TMP>)) {
next if $line =~ m/^\#/;
next if $line =~ m/^\s*$/;
if ($line =~ m|^([A-Z][A-Z])\s+\S+\s+(([^/]+)/\S+)\s|) {
}
}
- close (TMP);
+ close ($TMP);
return {
zones => $zones,
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) = @_;
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+$|) {
}
my $fssetup = {
- ext3 => {
- mkfs => 'mkfs.ext3 -F',
- mkfs_root_opt => '',
- mkfs_data_opt => '-m 0',
- root_mountopt => 'errors=remount-ro',
- },
ext4 => {
mkfs => 'mkfs.ext4 -F',
mkfs_root_opt => '',
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"); };
}
};
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;
return $swapsize;
}
+my sub chroot_chown {
+ my ($root, $path, %param) = @_;
+
+ my $recursive = $param{recursive} ? ' -R' : '';
+ my $user = $param{user};
+ die "can not chown without user parameter\n" if !defined($user);
+ my $group = $param{group} // $user;
+
+ syscmd("chroot $root /bin/chown $user:$group $recursive $path") == 0 ||
+ die "chroot: unable to change owner for '$path'\n";
+}
+
+my sub chroot_chmod {
+ my ($root, $path, %param) = @_;
+
+ my $recursive = $param{recursive} ? ' -R' : '';
+ my $mode = $param{mode};
+ die "can not chmod without mode parameter\n" if !defined($mode);
+
+ syscmd("chroot $root /bin/chmod $mode $recursive $path") == 0 ||
+ die "chroot: unable to change permission mode for '$path'\n";
+}
+
+sub prepare_proxmox_boot_esp {
+ my ($espdev, $targetdir) = @_;
+
+ syscmd("chroot $targetdir proxmox-boot-tool init $espdev") == 0 ||
+ die "unable to init ESP and install proxmox-boot loader on '$espdev'\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";
+
+ 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";
+ };
+ my $err = $@;
+
+ eval {
+ syscmd("umount $targetdir/boot/efi") == 0 ||
+ die "unable to umount $targetdir/boot/efi\n";
+ };
+ warn $@ if $@;
+
+ die "failed to prepare EFI boot using Grub on '$espdev': $err" if $err;
+}
sub extract_data {
my ($basefile, $targetdir) = @_;
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;
}
- &$udevadm_trigger_block();
+ $udevadm_trigger_block->();
btrfs_create($btrfs_partitions, $btrfs_mode);
} elsif ($use_zfs) {
- my ($devlist, $bootdevlist, $vdev) = get_zfs_raid_setup();
+ my ($devlist, $vdev) = get_zfs_raid_setup();
- my $disksize;
foreach my $hd (@$devlist) {
- &$clean_disk(@$hd[1]);
+ $clean_disk->(@$hd[1]);
}
- foreach my $hd (@$bootdevlist) {
+
+ # install esp/boot part on all, we can only win!
+ my $disksize;
+ for my $hd (@$devlist) {
my $devname = @$hd[1];
+ my $logical_bsize = @$hd[4];
- 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,
+ logical_bsize => $logical_bsize,
+ };
$disksize = $size;
}
- &$udevadm_trigger_block();
+ $udevadm_trigger_block->();
foreach my $di (@$bootdevinfo) {
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/;
}
+ 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";
}
}
}
+ 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");
$ifaces .=
"\nauto vmbr0\niface vmbr0 $ntype static\n" .
- "\taddress $ipaddress\n" .
- "\tnetmask $netmask\n" .
+ "\taddress $cidr\n" .
"\tgateway $gateway\n" .
"\tbridge_ports $ethdev\n" .
"\tbridge_stp off\n" .
} else {
$ifaces .= "auto $ethdev\n" .
"iface $ethdev $ntype static\n" .
- "\taddress $ipaddress\n" .
- "\tnetmask $netmask\n" .
+ "\taddress $cidr\n" .
"\tgateway $gateway\n";
}
# 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("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");
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";
+ write_config("root=ZFS=$zfspoolname/ROOT/$zfsrootvolname boot=zfs", "$targetdir/etc/kernel/cmdline");
+
}
diversion_remove($targetdir, "/usr/sbin/update-grub");
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";
+ my $native_4k_disk_bootable = 0;
+ foreach my $di (@$bootdevinfo) {
+ $native_4k_disk_bootable |= ($di->{logical_bsize} == 4096);
+ }
- 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";
+ foreach my $di (@$bootdevinfo) {
+ my $dev = $di->{devname};
+ if ($use_zfs) {
+ prepare_proxmox_boot_esp($di->{esp}, $targetdir);
+ } else {
+ 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 $@;
}
- }
- # 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";
+ eval {
+ if (my $esp = $di->{esp}) {
+ 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 (scalar(@$bootloader_err_list) > 0) {
+ $bootloader_err = "bootloader setup errors:\n";
+ map { $bootloader_err .= "- $_" } @$bootloader_err_list;
+ warn $bootloader_err;
+ }
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");
"$tmpdir/datacenter.cfg");
# save admin email
- write_config("user:root\@pam:1:0:::${mailto}::\n",
- "$tmpdir/user.cfg");
+ write_config("user:root\@pam:1:0:::${mailto}::\n", "$tmpdir/user.cfg");
# write storage.cfg
my $storage_cfg_fn = "$tmpdir/storage.cfg";
run_command("chroot $targetdir /usr/bin/create_pmxcfs_db /tmp/pve /var/lib/pve-cluster/config.db");
syscmd("rm -rf $tmpdir");
+ } elsif ($setup->{product} eq 'pbs') {
+ my $base_cfg_path = "/etc/proxmox-backup";
+ mkdir "$targetdir/$base_cfg_path";
+
+ chroot_chown($targetdir, $base_cfg_path, user => 'backup', recursive => 1);
+ chroot_chmod($targetdir, $base_cfg_path, mode => '0700');
+
+ my $user_cfg_fn = "$base_cfg_path/user.cfg";
+ write_config("user: root\@pam\n\temail ${mailto}\n", "$targetdir/$user_cfg_fn");
+ chroot_chown($targetdir, $user_cfg_fn, user => 'root', group => 'backup');
+ chroot_chmod($targetdir, $user_cfg_fn, mode => '0640');
}
};
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) {
syscmd("zpool export $zfspoolname");
}
+ if ($bootloader_err) {
+ $err = $err ? "$err\n$bootloader_err" : $bootloader_err;
+ }
+
die $err if $err;
}
$filename = $steps[$step_number]->{html} if !$filename;
- my $path = "${proxmox_libdir}/html/$filename";
-
- my $url = "file://$path";
+ my $htmldir = "${proxmox_libdir}/html";
+ my $path;
+ if (-f "$htmldir/$setup->{product}/$filename") {
+ $path = "$htmldir/$setup->{product}/$filename";
+ } elsif (-f "$htmldir/common/$filename") {
+ $path = "$htmldir/common/$filename";
+ } else {
+ # FIXME: die now already?
+ }
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/;
+ } elsif ($filename eq 'success.htm') {
+ my $addr = $ipversion == 6 ? "[${ipaddress}]" : "$ipaddress";
+ $data =~ s/__IPADDR__/$addr/g;
+ $data =~ s/__PORT__/$setup->{port}/g;
+
+ my $autoreboot_msg = $config_options->{autoreboot}
+ ? "Automatic reboot scheduled in $autoreboot_seconds seconds."
+ : '';
+ $data =~ s/__AUTOREBOOT_MSG__/$autoreboot_msg/;
}
+ $data =~ s/__FULL_PRODUCT_NAME__/$setup->{fullname}/g;
- $htmlview->load_html_string($data, $url);
+ # HACK: always set base-path to common path, all resources are there.
+ # NOTE: we could also use an overlayfs with lower=common upper=$product & work=/run/$tmp
+ $htmlview->load_html($data, "file://$htmldir/common/");
$last_display_change = time();
}
$window = Gtk3::Window->new();
$window->set_default_size(1024, 768);
$window->set_has_resize_grip(0);
+ $window->fullscreen() if !$opt_testmode;
$window->set_decorated(0) if !$opt_testmode;
my $vbox = Gtk3::VBox->new(0, 0);
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);
sub create_text_input {
my ($default, $text) = @_;
- my $hbox = Gtk3::HBox->new(0, 0);
+ my $hbox = Gtk3::Box->new('horizontal', 0);
my $label = Gtk3::Label->new($text);
$label->set_size_request(150, -1);
$label->set_alignment(1, 0.5);
$hbox->pack_start($label, 0, 0, 10);
my $e1 = Gtk3::Entry->new();
- $e1->set_width_chars(30);
+ $e1->set_width_chars(35);
$hbox->pack_start($e1, 0, 0, 0);
$e1->set_text($default);
return ($hbox, $e1);
}
+sub create_cidr_inputs {
+ my ($default_ip, $default_mask) = @_;
+
+ my $hbox = Gtk3::Box->new('horizontal', 0);
+
+ my $label = Gtk3::Label->new('IP Address (CIDR)');
+ $label->set_size_request(150, -1);
+ $label->set_alignment(1, 0.5);
+ $hbox->pack_start($label, 0, 0, 10);
+
+ my $ip_el = Gtk3::Entry->new();
+ $ip_el->set_width_chars(28);
+ $hbox->pack_start($ip_el, 0, 0, 0);
+ $ip_el->set_text($default_ip);
+
+ $label = Gtk3::Label->new('/');
+ $label->set_size_request(10, -1);
+ $label->set_alignment(0.5, 0.5);
+ $hbox->pack_start($label, 0, 0, 2);
+
+ my $cidr_el = Gtk3::Entry->new();
+ $cidr_el->set_width_chars(3);
+ $hbox->pack_start($cidr_el, 0, 0, 0);
+ $cidr_el->set_text($default_mask);
+
+ return ($hbox, $ip_el, $cidr_el);
+}
sub get_ip_config {
$default = $index if !$default;
$ifaces->{"$index"}->{"$family"} = {
+ prefix => $prefix,
mask => $mask,
addr => $ip,
};
cleanup_view();
display_html();
- my $vbox = Gtk3::VBox->new(0, 0);
- $inbox->pack_start($vbox, 1, 0, 0);
- my $hbox = Gtk3::HBox->new(0, 0);
- $vbox->pack_start($hbox, 0, 0, 10);
- my $vbox2 = Gtk3::VBox->new(0, 0);
- $hbox->add($vbox2);
+ my $vcontainer = Gtk3::Box->new('vertical', 0);
+ $inbox->pack_start($vcontainer, 1, 0, 0);
+ my $hcontainer = Gtk3::Box->new('horizontal', 0);
+ $vcontainer->pack_start($hcontainer, 0, 0, 10);
+ my $vbox = Gtk3::Box->new('vertical', 0);
+ $hcontainer->add($vbox);
my $ipaddr_text = $config->{ipaddress} // "192.168.100.2";
- my $ipbox;
- ($ipbox, $ipconf_entry_addr) =
- create_text_input($ipaddr_text, 'IP Address:');
-
- my $netmask_text = $config->{netmask} // "255.255.255.0";
- my $maskbox;
- ($maskbox, $ipconf_entry_mask) =
- create_text_input($netmask_text, 'Netmask:');
+ my $netmask_text = $config->{netmask} // "24";
+ my $cidr_box;
+ ($cidr_box, $ipconf_entry_addr, $ipconf_entry_mask) =
+ create_cidr_inputs($ipaddr_text, $netmask_text);
my $device_cb = Gtk3::ComboBoxText->new();
$device_cb->set_active(0);
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})
if $iface->{inet}->{addr} || $iface->{inet6}->{addr};
- $ipconf_entry_mask->set_text($iface->{inet}->{mask} || $iface->{inet6}->{mask})
- if $iface->{inet}->{mask} || $iface->{inet6}->{mask};
+ $ipconf_entry_mask->set_text($iface->{inet}->{prefix} || $iface->{inet6}->{prefix})
+ if $iface->{inet}->{prefix} || $iface->{inet6}->{prefix};
};
my $i = 0;
$devicebox->pack_start($label, 0, 0, 10);
$devicebox->pack_start($device_cb, 0, 0, 0);
- $vbox2->pack_start($devicebox, 0, 0, 2);
+ $vbox->pack_start($devicebox, 0, 0, 2);
my $hn = $config->{fqdn} // "$setup->{product}." . ($ipconf->{domain} // "example.invalid");
- my ($hostbox, $hostentry) =
- create_text_input($hn, 'Hostname (FQDN):');
- $vbox2->pack_start($hostbox, 0, 0, 2);
-
- $vbox2->pack_start($ipbox, 0, 0, 2);
+ my ($hostbox, $hostentry) = create_text_input($hn, 'Hostname (FQDN):');
+ $vbox->pack_start($hostbox, 0, 0, 2);
- $vbox2->pack_start($maskbox, 0, 0, 2);
+ $vbox->pack_start($cidr_box, 0, 0, 2);
$gateway = $config->{gateway} // $ipconf->{gateway} || '192.168.100.1';
($gwbox, $ipconf_entry_gw) =
create_text_input($gateway, 'Gateway:');
- $vbox2->pack_start($gwbox, 0, 0, 2);
+ $vbox->pack_start($gwbox, 0, 0, 2);
$dnsserver = $config->{dnsserver} // $ipconf->{dnsserver} || $gateway;
($dnsbox, $ipconf_entry_dns) =
create_text_input($dnsserver, 'DNS Server:');
- $vbox2->pack_start($dnsbox, 0, 0, 0);
+ $vbox->pack_start($dnsbox, 0, 0, 0);
$inbox->show_all;
set_next(undef, sub {
$text = $ipconf_entry_mask->get_text();
$text =~ s/^\s+//;
$text =~ s/\s+$//;
- if (($ipversion == 6) && ($text =~ m/^(\d+)$/) && ($1 >= 8) && ($1 <= 126)) {
+ if ($ipversion == 6 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 126) {
$netmask = $text;
- } elsif (($ipversion == 4) && defined($ipv4_mask_hash->{$text})) {
+ } elsif ($ipversion == 4 && ($text =~ m/^(\d+)$/) && $1 >= 8 && $1 <= 32) {
$netmask = $text;
+ } elsif ($ipversion == 4 && defined($ipv4_mask_hash->{$text})) {
+ # costs nothing to handle 255.x.y.z style masks, so continue to allow it
+ $netmask = $ipv4_mask_hash->{$text};
} else {
display_message("Netmask is not valid.");
$ipconf_entry_mask->grab_focus();
return;
}
+ $cidr = "$ipaddress/$netmask";
$config->{netmask} = $netmask;
$text = $ipconf_entry_gw->get_text();
cleanup_view();
- my $ack_template = "${proxmox_libdir}/html/ack_template.htm";
- my $ack_html = "${proxmox_libdir}/html/$steps[$step_number]->{html}";
+ my $vbox = Gtk3::VBox->new(0, 0);
+ $inbox->pack_start($vbox, 1, 0, 0);
+ #my $hbox = Gtk3::HBox->new(0, 0);
+ #$vbox->pack_start($hbox, 0, 0, 10);
+
+ my $reboot_checkbox = Gtk3::CheckButton->new('Automatically reboot after successful installation');
+ $reboot_checkbox->set_active(1);
+ $reboot_checkbox->signal_connect ("toggled" => sub {
+ my $cb = shift;
+ $config_options->{autoreboot} = $cb->get_active();
+ });
+ $vbox->pack_start($reboot_checkbox, 0, 0, 2);
+
+ my $ack_template = "${proxmox_libdir}/html/common/ack_template.htm";
+ my $ack_html = "${proxmox_libdir}/html/$setup->{product}/$steps[$step_number]->{html}";
my $html_data = file_get_contents($ack_template);
my %config_values = (
__interface__ => $ipconf->{ifaces}->{$ipconf->{selected}}->{name},
__hostname__ => $hostname,
__ip__ => $ipaddress,
+ __cidr__ => $cidr,
__netmask__ => $netmask,
__gateway__ => $gateway,
__dnsserver__ => $dnsserver,
display_html();
+ $inbox->show_all;
+
set_next(undef, sub {
$step_number++;
create_extract_view();
my $text = "$devname (";
if ($size >= 1024) {
$size = int($size/1024); # size in GB
- $text .= "${size}GB";
+ if ($size >= 1024) {
+ $size = int($size/1024); # size in GB
+ $text .= "${size}TiB";
+ } else {
+ $text .= "${size}GiB";
+ }
} else {
- $text .= "${size}MB";
+ $text .= "${size}MiB";
}
$text .= ", $model" if $model;
}
}
+my $last_layout;
+my $country_layout;
sub update_layout {
my ($cb, $kmap) = @_;
$i++;
}
- $cb->set_active($ind || $def || 0);
+ my $val = $ind || $def || 0;
+
+ if (!defined($kmap)) {
+ $last_layout //= $val;
+ } elsif (!defined($country_layout) || $country_layout != $val) {
+ $last_layout = $country_layout = $val;
+ }
+ $cb->set_active($last_layout);
}
my $lastzonecb;
$hbox2->pack_start($pwe2, 0, 0, 0);
my $hbox3 = Gtk3::HBox->new(0, 0);
- $label = Gtk3::Label->new("E-Mail");
+ $label = Gtk3::Label->new("Email");
$label->set_size_request(150, -1);
$label->set_alignment(1, 0.5);
$hbox3->pack_start($label, 0, 0, 10);
}
my $t3 = $eme->get_text;
- if ($t3 !~ m/^\S+\@\S+\.\S+$/) {
- display_message("E-Mail does not look like a valid address" .
+ if ($t3 !~ m/^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$/) {
+ display_message("Email does not look like a valid address" .
" (user\@domain.tld)");
$eme->grab_focus();
return;
}
if ($t3 eq 'mail@example.invalid') {
- display_message("Please enter a valid E-Mail address");
+ display_message("Please enter a valid Email address");
$eme->grab_focus();
return;
}
}
+my $installer_kmap;
sub create_country_view {
cleanup_view();
$kmapcb->signal_connect ('changed' => sub {
my $sel = $kmapcb->get_active_text();
+ $last_layout = $kmapcb->get_active();
if (my $kmap = $cmap->{kmaphash}->{$sel}) {
my $xkmap = $cmap->{kmap}->{$kmap}->{x11};
my $xvar = $cmap->{kmap}->{$kmap}->{x11var};
- syscmd ("setxkbmap $xkmap $xvar") if !$opt_testmode;
$keymap = $kmap;
+
+ return if (defined($installer_kmap) && $installer_kmap eq $kmap);
+
+ $installer_kmap = $keymap;
+
+ if (! $opt_testmode) {
+ syscmd ("setxkbmap $xkmap $xvar");
+
+ my $kbd_config = qq{
+ XKBLAYOUT="$xkmap"
+ XKBVARIANT="$xvar"
+ BACKSPACE="guess"
+ };
+ $kbd_config =~ s/^\s+//gm;
+
+ run_in_background( sub {
+ write_config($kbd_config, '/etc/default/keyboard');
+ system("setupcon");
+ });
+ }
}
});
$grid->set_row_spacing(10);
$grid->set_hexpand(1);
- $grid->set_margin_start(5);
- $grid->set_margin_end(5);
+ $grid->set_margin_start(10);
+ $grid->set_margin_end(20);
$grid->set_margin_top(5);
$grid->set_margin_bottom(5);
};
my $create_raid_disk_grid = sub {
+ my $hd_count = scalar(@$hds);
my $disk_labeled_widgets = [];
- for (my $i = 0; $i < @$hds; $i++) {
+ for (my $i = 0; $i < $hd_count; $i++) {
my $disk_selector = Gtk3::ComboBoxText->new();
$disk_selector->append_text("-- do not use --");
$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 {
push @$disk_labeled_widgets, "Harddisk $i", $disk_selector;
}
+ my $clear_all_button = Gtk3::Button->new('_Deselect All');
+ if ($hd_count > 3) {
+ $clear_all_button->signal_connect('clicked', sub {
+ my $is_widget = 0;
+ for my $disk_selector (@$disk_labeled_widgets) {
+ $disk_selector->set_active(0) if $is_widget;
+ $is_widget ^= 1;
+ }
+ });
+ $clear_all_button->set_visible(1);
+ }
+
my $scrolled_window = Gtk3::ScrolledWindow->new();
$scrolled_window->set_hexpand(1);
- $scrolled_window->set_propagate_natural_height(1) if @$hds > 4;
- $scrolled_window->add(&$create_label_widget_grid($disk_labeled_widgets));
+ $scrolled_window->set_propagate_natural_height(1) if $hd_count > 4;
+
+ my $diskgrid = $create_label_widget_grid->($disk_labeled_widgets);
+
+ $scrolled_window->add($diskgrid);
$scrolled_window->set_policy('never', 'automatic');
+ $scrolled_window->set_visible(1);
+ $scrolled_window->set_min_content_height(190);
+
+ my $vbox = Gtk3::Box->new('vertical', 0);
+ $vbox->pack_start($scrolled_window, 1, 1, 10);
- return $scrolled_window;
-# &$create_label_widget_grid($disk_labeled_widgets)
+ my $hbox = Gtk3::Box->new('horizontal', 0);
+ $hbox->pack_end($clear_all_button, 0, 0, 20);
+ $hbox->set_visible(1);
+ $vbox->pack_end($hbox, 0, 0, 0);
+
+ return $vbox;
};
# shared between different ui parts (e.g., ZFS and "normal" single disk FS)
my $create_raid_advanced_grid = sub {
my $labeled_widgets = [];
- my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9,13,1);
+ my $spinbutton_ashift = Gtk3::SpinButton->new_with_range(9, 13, 1);
$spinbutton_ashift->set_tooltip_text("zpool ashift property (pool sector size, default 2^12)");
$spinbutton_ashift->signal_connect ("value-changed" => sub {
my $w = shift;
push @$labeled_widgets, "copies", $spinbutton_copies;
push @$labeled_widgets, "hdsize", $get_hdsize_spinbtn->();
- return &$create_label_widget_grid($labeled_widgets);;
+ return $create_label_widget_grid->($labeled_widgets);;
};
sub create_hdoption_view {
my $contarea = $dialog->get_content_area();
my $hbox2 = Gtk3::Box->new('horizontal', 0);
- $contarea->pack_start($hbox2, 1, 1, 10);
+ $contarea->pack_start($hbox2, 1, 1, 5);
my $grid = Gtk3::Grid->new();
$grid->set_column_spacing(10);
$grid->set_row_spacing(10);
- $hbox2->pack_start($grid, 1, 0, 10);
+ $hbox2->pack_start($grid, 1, 0, 5);
my $row = 0;
# Filesystem type
-
my $label0 = Gtk3::Label->new("Filesystem");
$label0->set_alignment (1, 0.5);
$grid->attach($label0, 0, $row, 1, 1);
my $fstypecb = Gtk3::ComboBoxText->new();
-
- my $fstype = ['ext3', 'ext4', 'xfs',
- 'zfs (RAID0)', 'zfs (RAID1)',
- 'zfs (RAID10)', 'zfs (RAIDZ-1)',
- 'zfs (RAIDZ-2)', 'zfs (RAIDZ-3)'];
-
+ my $fstype = [
+ 'ext4',
+ 'xfs',
+ 'zfs (RAID0)',
+ 'zfs (RAID1)',
+ 'zfs (RAID10)',
+ 'zfs (RAIDZ-1)',
+ 'zfs (RAIDZ-2)',
+ 'zfs (RAIDZ-3)',
+ ];
push @$fstype, 'btrfs (RAID0)', 'btrfs (RAID1)', 'btrfs (RAID10)'
if $setup->{enable_btrfs};
my $tcount = 0;
foreach my $tmp (@$fstype) {
$fstypecb->append_text($tmp);
- $fstypecb->set_active ($tcount)
- if $config_options->{filesys} eq $tmp;
+ $fstypecb->set_active ($tcount) if $config_options->{filesys} eq $tmp;
$tcount++;
}
$grid->attach($sep, 0, $row, 2, 1);
$row++;
+ my $hw_raid_note = Gtk3::Label->new(
+ "Note: ZFS is not compatible with hardware RAID controllers, for details see the 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
$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();
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 get_zfs_raid_setup {
+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};
my $devlist = &$get_raid_devlist();
my $diskcount = scalar(@$devlist);
die "$filesys needs at least one device\n" if $diskcount < 1;
- my $bootdevlist = [];
-
my $cmd= '';
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)') {
$cmd .= ' mirror ';
my $hd = @$devlist[0];
my $expected_size = @$hd[2]; # all disks need approximately same size
- foreach $hd (@$devlist) {
+ foreach my $hd (@$devlist) {
zfs_mirror_size_check($expected_size, @$hd[2]);
+ legacy_bios_4k_check(@$hd[4]);
$cmd .= " @$hd[1]";
- push @$bootdevlist, $hd;
}
} elsif ($filesys eq 'zfs (RAID10)') {
die "zfs (RAID10) needs at least 4 device\n" if $diskcount < 4;
die "zfs (RAID10) needs an even number of devices\n" if $diskcount & 1;
- push @$bootdevlist, @$devlist[0], @$devlist[1];
-
for (my $i = 0; $i < $diskcount; $i+=2) {
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];
}
my $hd = @$devlist[0];
my $expected_size = @$hd[2]; # all disks need approximately same size
$cmd .= " raidz$level";
- foreach $hd (@$devlist) {
+ foreach my $hd (@$devlist) {
zfs_mirror_size_check($expected_size, @$hd[2]);
+ legacy_bios_4k_check(@$hd[4]);
$cmd .= " @$hd[1]";
- push @$bootdevlist, $hd;
}
} else {
die "unknown zfs mode '$filesys'\n";
}
- return ($devlist, $bootdevlist, $cmd);
+ return ($devlist, $cmd);
}
sub get_btrfs_raid_setup {
return ($devlist, $mode);
}
+my $last_hd_selected = 0;
sub create_hdsel_view {
$prev_btn->set_sensitive(1); # enable previous button at this point
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));
}
$target_hd_combo->set_visible(0);
$target_hd_combo->set_no_show_all(1);
}
- $target_hd_combo->set_active($config_options->{chosen_hd} // 0);
+ $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;
});
}
$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 ];
}
} else {
cleanup_view();
display_html("success.htm");
+
+ if ($config_options->{autoreboot}) {
+ Glib::Timeout->add(1000, sub {
+ if ($autoreboot_seconds > 0) {
+ $autoreboot_seconds--;
+ display_html("success.htm");
+ } else {
+ exit(0);
+ }
+ });
+ }
}
}
cleanup_view();
+ if (int($total_memory) < 1024) {
+ display_error("Less than 1 GiB of usable memory detected, installation will probably fail.\n\n".
+ "See 'System Requirements' in the $setup->{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.")
}
};
Gtk3->main;
+# reap left over zombie processes
+while ((my $child = waitpid(-1, POSIX::WNOHANG)) > 0) {
+ print "reaped child $child\n";
+}
+
exit 0;