use PVE::Corosync;
use PVE::INotify;
use PVE::JSONSchema;
+use PVE::NodeConfig;
use PVE::RPCEnvironment;
use PVE::Storage;
+use PVE::Storage::Plugin;
use PVE::Tools qw(run_command split_list);
+use PVE::QemuConfig;
use PVE::QemuServer;
use PVE::VZDump::Common;
-use File::Slurp;
use Term::ANSIColor;
use PVE::CLIHandler;
sub check_custom_pool_roles {
log_info("Checking custom roles for pool permissions..");
- my $raw = read_file('/etc/pve/user.cfg');
+ my $raw = eval { PVE::Tools::file_get_contents('/etc/pve/user.cfg'); };
+ if ($@) {
+ log_fail("Failed to read '/etc/pve/user.cfg' - $@");
+ return;
+ }
my $roles = {};
- while ($raw =~ /^\s*role:(.*?):(.*?):(.*?)\s*$/gm) {
- my ($role, $privlist) = ($1, $2);
+ while ($raw =~ /^\s*(.+?)\s*$/gm) {
+ my $line = $1;
+ my @data;
+
+ foreach my $d (split (/:/, $line)) {
+ $d =~ s/^\s+//;
+ $d =~ s/\s+$//;
+ push @data, $d
+ }
+ my $et = shift @data;
+ next if $et ne 'role';
+
+ my ($role, $privlist) = @data;
if (!PVE::AccessControl::verify_rolename($role, 1)) {
warn "user config - ignore role '$role' - invalid characters in role name\n";
next;
}
}
+my sub check_max_length {
+ my ($raw, $max_length, $warning) = @_;
+ log_warn($warning) if defined($raw) && length($raw) > $max_length;
+}
+
+sub check_description_lengths {
+ log_info("Checking node and guest description/note legnth..");
+
+ my @affected_nodes = grep {
+ my $desc = PVE::NodeConfig::load_config($_)->{desc};
+ defined($desc) && length($desc) > 64 * 1024
+ } PVE::Cluster::get_nodelist();
+
+ if (scalar(@affected_nodes) > 0) {
+ log_warn("Node config description of the following nodes too long for new limit of 64 KiB:\n "
+ . join(', ', @affected_nodes));
+ } else {
+ log_pass("All node config descriptions fit in the new limit of 64 KiB");
+ }
+
+ my $affected_guests = [];
+
+ my $cts = PVE::LXC::config_list();
+ for my $vmid (sort { $a <=> $b } keys %$cts) {
+ my $desc = PVE::LXC::Config->load_config($vmid)->{description};
+ push @$affected_guests, "CT $vmid" if defined($desc) && length($desc) > 8 * 1024;
+ }
+ my $vms = PVE::QemuServer::config_list();
+ for my $vmid (sort { $a <=> $b } keys %$vms) {
+ my $desc = PVE::QemuConfig->load_config($vmid)->{description};
+ push @$affected_guests, "VM $vmid" if defined($desc) && length($desc) > 8 * 1024;
+ }
+ if (scalar($affected_guests->@*) > 0) {
+ log_warn("Node config description of the following nodes too long for new limit of 64 KiB:\n"
+ ." * " . join("\n * ", $affected_guests->@*));
+ } else {
+ log_pass("All guest config descriptions fit in the new limit of 8 KiB");
+ }
+}
+
+sub check_storage_content {
+ log_info("Checking storage content type configuration..");
+
+ my $found_referenced;
+ my $found_unreferenced;
+ my $pass = 1;
+
+ my $storage_cfg = PVE::Storage::config();
+
+ my $potentially_affected = {};
+ my $referenced_volids = {};
+
+ for my $storeid (keys $storage_cfg->{ids}->%*) {
+ my $scfg = $storage_cfg->{ids}->{$storeid};
+
+ next if !PVE::Storage::storage_check_enabled($storage_cfg, $storeid, undef, 1);
+
+ my $valid_content = PVE::Storage::Plugin::valid_content_types($scfg->{type});
+
+ if (scalar(keys $scfg->{content}->%*) == 0 && !$valid_content->{none}) {
+ $pass = 0;
+ log_fail("storage '$storeid' does not support configured content type 'none'");
+ delete $scfg->{content}->{none}; # scan for guest images below
+ }
+
+ next if $scfg->{content}->{images} && $scfg->{content}->{rootdir};
+
+ # Skip 'iscsi(direct)' (and foreign plugins with potentially similiar behavior) with 'none',
+ # because that means "use LUNs directly" and vdisk_list() in PVE 6.x still lists those.
+ # It's enough to *not* skip 'dir', because it is the only other storage that supports 'none'
+ # and 'images' or 'rootdir', hence being potentially misconfigured.
+ next if $scfg->{type} ne 'dir' && $scfg->{content}->{none};
+
+ eval { PVE::Storage::activate_storage($storage_cfg, $storeid) };
+ if (my $err = $@) {
+ log_warn("activating '$storeid' failed - $err");
+ next;
+ }
+
+ my $res = eval { PVE::Storage::vdisk_list($storage_cfg, $storeid); };
+ if (my $err = $@) {
+ log_warn("listing images on '$storeid' failed - $err");
+ next;
+ }
+ my @volids = map { $_->{volid} } $res->{$storeid}->@*;
+
+ for my $volid (@volids) {
+ $potentially_affected->{$volid} = 1;
+ }
+
+ my $number = scalar(@volids);
+ if ($number > 0 && !$scfg->{content}->{images} && !$scfg->{content}->{rootdir}) {
+ log_info("storage '$storeid' - neither content type 'images' nor 'rootdir' configured"
+ .", but found $number guest volume(s)");
+ }
+ }
+
+ my $check_volid = sub {
+ my ($volid, $vmid, $vmtype, $reference) = @_;
+
+ $referenced_volids->{$volid} = 1 if $reference ne 'unreferenced';
+
+ my $guesttext = $vmtype eq 'qemu' ? 'VM' : 'CT';
+ my $prefix = "$guesttext $vmid - volume '$volid' ($reference)";
+
+ my ($storeid) = PVE::Storage::parse_volume_id($volid, 1);
+ return if !defined($storeid);
+
+ my $scfg = $storage_cfg->{ids}->{$storeid};
+ if (!$scfg) {
+ $pass = 0;
+ log_warn("$prefix - storage does not exist!");
+ return;
+ }
+
+ # cannot use parse_volname for containers, as it can return 'images'
+ # but containers cannot have ISO images attached, so assume 'rootdir'
+ my $vtype = 'rootdir';
+ if ($vmtype eq 'qemu') {
+ ($vtype) = eval { PVE::Storage::parse_volname($storage_cfg, $volid); };
+ return if $@;
+ }
+
+ if (!$scfg->{content}->{$vtype}) {
+ $found_referenced = 1 if $reference ne 'unreferenced';
+ $found_unreferenced = 1 if $reference eq 'unreferenced';
+ $pass = 0;
+ log_warn("$prefix - storage does not have content type '$vtype' configured.");
+ }
+ };
+
+ my $guests = {};
+
+ my $cts = PVE::LXC::config_list();
+ for my $vmid (sort { $a <=> $b } keys %$cts) {
+ $guests->{$vmid} = 'lxc';
+
+ my $conf = PVE::LXC::Config->load_config($vmid);
+
+ my $volhash = {};
+
+ my $check = sub {
+ my ($ms, $mountpoint, $reference) = @_;
+
+ my $volid = $mountpoint->{volume};
+ return if !$volid || $mountpoint->{type} ne 'volume';
+
+ return if $volhash->{$volid}; # volume might be referenced multiple times
+
+ $volhash->{$volid} = 1;
+
+ $check_volid->($volid, $vmid, 'lxc', $reference);
+ };
+
+ my $opts = { include_unused => 1 };
+ PVE::LXC::Config->foreach_volume_full($conf, $opts, $check, 'in config');
+ for my $snapname (keys $conf->{snapshots}->%*) {
+ my $snap = $conf->{snapshots}->{$snapname};
+ PVE::LXC::Config->foreach_volume_full($snap, $opts, $check, "in snapshot '$snapname'");
+ }
+ }
+
+ my $vms = PVE::QemuServer::config_list();
+ for my $vmid (sort { $a <=> $b } keys %$vms) {
+ $guests->{$vmid} = 'qemu';
+
+ my $conf = PVE::QemuConfig->load_config($vmid);
+
+ my $volhash = {};
+
+ my $check = sub {
+ my ($key, $drive, $reference) = @_;
+
+ my $volid = $drive->{file};
+ return if $volid =~ m|^/|;
+
+ return if $volhash->{$volid}; # volume might be referenced multiple times
+
+ $volhash->{$volid} = 1;
+
+ $check_volid->($volid, $vmid, 'qemu', $reference);
+ };
+
+ my $opts = {
+ extra_keys => ['vmstate'],
+ include_unused => 1,
+ };
+ # startup from a suspended state works even without 'images' content type on the
+ # state storage, so do not check 'vmstate' for $conf
+ PVE::QemuConfig->foreach_volume_full($conf, { include_unused => 1 }, $check, 'in config');
+ for my $snapname (keys $conf->{snapshots}->%*) {
+ my $snap = $conf->{snapshots}->{$snapname};
+ PVE::QemuConfig->foreach_volume_full($snap, $opts, $check, "in snapshot '$snapname'");
+ }
+ }
+
+ if ($found_referenced) {
+ log_warn("Proxmox VE 7.0 enforces stricter content type checks. The guests above " .
+ "might not work until the storage configuration is fixed.");
+ }
+
+ for my $volid (sort keys $potentially_affected->%*) {
+ next if $referenced_volids->{$volid}; # already checked
+
+ my (undef, undef, $vmid) = PVE::Storage::parse_volname($storage_cfg, $volid);
+ my $vmtype = $guests->{$vmid};
+ next if !$vmtype;
+
+ $check_volid->($volid, $vmid, $vmtype, 'unreferenced');
+ }
+
+ if ($found_unreferenced) {
+ log_warn("When migrating, Proxmox VE 7.0 only scans storages with the appropriate " .
+ "content types for unreferenced guest volumes.");
+ }
+
+ if ($pass) {
+ log_pass("no problems found");
+ }
+}
+
sub check_misc {
print_header("MISCELLANEOUS CHECKS");
my $ssh_config = eval { PVE::Tools::file_get_contents('/root/.ssh/config') };
$log_systemd_unit_state->('pvestatd.service');
my $root_free = PVE::Tools::df('/', 10);
- log_warn("Less than 2G free space on root file system.")
- if defined($root_free) && $root_free->{avail} < 2*1024*1024*1024;
+ log_warn("Less than 4 GiB free space on root file system.")
+ if defined($root_free) && $root_free->{avail} < 4*1024*1024*1024;
log_info("Checking for running guests..");
my $running_guests = 0;
check_backup_retention_settings();
check_cifs_credential_location();
check_custom_pool_roles();
+ check_description_lengths();
+ check_storage_content();
}
__PACKAGE__->register_method ({