1 package Proxmox
::Install
::RunEnv
;
7 use JSON
qw(from_json to_json);
10 use Proxmox
::Sys
::Command
qw(run_command CMD_FINISHED);
11 use Proxmox
::Sys
::File
qw(file_read_firstline);
12 use Proxmox
::Sys
::Block
;
13 use Proxmox
::Sys
::Net
;
15 use Proxmox
::Install
::ISOEnv
;
17 my sub fromjs
: prototype($) {
18 return from_json
($_[0], { utf8
=> 1 });
21 my $mem_total = undef;
22 # Returns the system memory size in MiB, and falls back to 512 MiB if it
23 # could not be determined.
24 sub query_total_memory
: prototype() {
25 return $mem_total if defined($mem_total);
27 open (my $MEMINFO, '<', '/proc/meminfo');
29 my $res = 512; # default to 512 if something goes wrong
30 while (my $line = <$MEMINFO>) {
31 if ($line =~ m/^MemTotal:\s+(\d+)\s*kB/i) {
32 $res = int ($1 / 1024);
41 my $cpu_hvm_support = undef;
42 sub query_cpu_hvm_support
: prototype() {
43 return $cpu_hvm_support if defined($cpu_hvm_support);
45 open (my $CPUINFO, '<', '/proc/cpuinfo');
48 while (my $line = <$CPUINFO>) {
49 if ($line =~ /^flags\s*:.*(vmx|svm)/m) {
56 $cpu_hvm_support = $res;
57 return $cpu_hvm_support;
64 # mac => <mac address>,
68 # family => <inet|inet6>,
69 # address => <mac address>,
74 my sub query_netdevs
: prototype() {
78 # FIXME: not the same as the battle proven way we used in the installer for years?
79 my $interfaces = fromjs
(qx
/ip --json address show/);
81 for my $if (@$interfaces) {
82 my ($index, $name, $state, $mac, $addresses) =
83 $if->@{qw(ifindex ifname operstate address addr_info)};
85 next if !$name || $name eq 'lo'; # could also check flags for LOOPBACK..
88 if (uc($state) eq 'UP') {
89 for my $addr (@$addresses) {
90 next if $addr->{scope
} eq 'link';
92 my ($family, $addr, $prefix) = $addr->@{qw(family local prefixlen)};
108 $ifs->{$name}->{addresses
} = \
@valid_addrs if @valid_addrs;
128 my sub query_routes
: prototype() {
129 my ($gateway4, $gateway6);
131 log_info
("query routes");
132 my $route4 = fromjs
(qx
/ip -4 --json route show/);
133 for my $route (@$route4) {
134 if ($route->{dst
} eq 'default') {
136 dev
=> $route->{dev
},
137 gateway
=> $route->{gateway
},
143 my $route6 = fromjs
(qx
/ip -6 --json route show/);
144 for my $route (@$route6) {
145 if ($route->{dst
} eq 'default') {
147 dev
=> $route->{dev
},
148 gateway
=> $route->{gateway
},
155 $routes->{gateway4
} = $gateway4 if $gateway4;
156 $routes->{gateway6
} = $gateway6 if $gateway6;
161 # If `/etc/resolv.conf` fails to open this returns nothing.
162 # Otherwise it returns a hash:
164 # dns => <first dns entry>,
166 my sub query_dns
: prototype() {
167 log_info
("query DNS from resolv.conf (managed by DHCP client)");
168 open my $fh , '<', '/etc/resolv.conf' or return;
172 while (defined(my $line = <$fh>)) {
173 if ($line =~ /^nameserver\s+(\S+)/) {
175 } elsif (!defined($domain) && $line =~ /^domain\s+(\S+)/) {
182 @dns ?
(dns
=> \
@dns) : (),
186 # Uses `traceroute` and `geoiplookup`/`geoiplookup6` to figure out the current country.
187 # Has a 10s timeout and uses the stops at the first entry found in the geoip database.
188 my sub detect_country_tracing_to
: prototype($$) {
189 my ($ipver, $destination) = @_;
191 print STDERR
"trying to detect country...\n";
193 my $traceroute_cmd = ['traceroute', "-$ipver", '-N', '1', '-q', '1', '-n', $destination];
194 my $geoip_bin = ($ipver == 6) ?
'geoiplookup6' : 'geoiplookup';
199 local $SIG{ALRM
} = sub { die "timed out!\n" };
200 $previous_alarm = alarm (10);
202 run_command
($traceroute_cmd, sub {
205 log_debug
("DC TRACEROUTE: $line");
206 if ($line =~ m/^\s*\d+\s+(\S+)\s/) {
207 my $geoip = qx
/$geoip_bin $1/;
208 log_debug
("DC GEOIP: $geoip");
210 if ($geoip =~ m/GeoIP Country Edition:\s*([A-Z]+),/) {
212 log_info
("DC FOUND: $country\n");
221 alarm ($previous_alarm);
224 die "unable to detect country - $err\n";
226 print STDERR
"detected country: " . uc($country) . "\n";
232 # Returns the entire environment as a hash.
234 # country => <short country>,
235 # ipconf = <see Proxmox::Sys::Net::get_ip_config()>,
236 # kernel_cmdline = <contents of /proc/cmdline>,
237 # total_memory = <memory size in MiB>,
238 # hvm_supported = <1 if the CPU supports hardware-accelerated virtualization>,
239 # boot_type = <either 'efi' or 'bios'>,
240 # disks => <see Proxmox::Sys::Block::hd_list()>,
242 # interfaces => <see query_netdevs()>,
243 # routes => <see query_routes()>,
244 # dns => <see query_dns()>,
247 sub query_installation_environment
: prototype() {
248 # check first if somebody already cached this for us and re-use that
249 my $run_env_file = Proxmox
::Install
::ISOEnv
::get
('run-env-cache-file');
250 if (-f
"$run_env_file") {
251 log_info
("re-using cached runtime env from $run_env_file");
252 my $cached_env = eval {
253 my $run_env_raw = Proxmox
::Sys
::File
::file_read_all
($run_env_file);
254 return fromjs
($run_env_raw); # returns from eval
256 log_error
("failed to parse cached runtime env - $@") if $@;
257 return $cached_env if defined($cached_env) && scalar keys $cached_env->%*;
258 log_warn
("cached runtime env seems empty, query everything (again)");
260 # else re-query everything
263 my $routes = query_routes
();
265 log_info
("query block devices");
266 $output->{disks
} = Proxmox
::Sys
::Block
::get_cached_disks
();
267 $output->{network
} = {
268 interfaces
=> query_netdevs
(),
273 # avoid serializing out null or an empty string, that can trip up the UIs
274 if (my $fqdn = Proxmox
::Sys
::Net
::get_dhcp_fqdn
()) {
275 $output->{network
}->{hostname
} = $fqdn;
278 # FIXME: move whatever makes sense over to Proxmox::Sys::Net:: and keep that as single source,
279 # it can then use some different structure just fine (after adapting the GTK GUI to that) but
280 # **never** to (slightly different!) things for the same stuff...
281 $output->{ipconf
} = Proxmox
::Sys
::Net
::get_ip_config
();
283 $output->{kernel_cmdline
} = file_read_firstline
("/proc/cmdline");
284 $output->{total_memory
} = query_total_memory
();
285 $output->{hvm_supported
} = query_cpu_hvm_support
();
286 $output->{boot_type
} = -d
'/sys/firmware/efi' ?
'efi' : 'bios';
290 if ($routes->{gateway4
}) {
291 log_info
("trace country via IPv4");
292 $country = eval { detect_country_tracing_to
(4 => '8.8.8.8') };
293 $err = $@ if !$country;
296 if (!$country && $routes->{gateway6
}) {
297 log_info
("trace country via IPv6");
298 $country = eval { detect_country_tracing_to
(6 => '2001:4860:4860::8888') };
299 $err = $@ if !$country;
302 if (defined($country)) {
303 $output->{country
} = $country;
305 warn ($err // "unable to detect country\n");
311 # OpenZFS specifies 64 MiB as the absolute minimum:
312 # https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max
313 our $ZFS_ARC_MIN_SIZE_MIB = 64; # MiB
315 # See https://bugzilla.proxmox.com/show_bug.cgi?id=4829
316 our $ZFS_ARC_MAX_SIZE_MIB = 16 * 1024; # 16384 MiB = 16 GiB
317 our $ZFS_ARC_SYSMEM_PERCENTAGE = 0.1; # use 10% of available system memory by default
319 # Calculates the default upper limit for the ZFS ARC size.
320 # Returns the default ZFS maximum ARC size in MiB.
321 sub default_zfs_arc_max
{
322 # Use ZFS default on non-PVE
323 return 0 if Proxmox
::Install
::ISOEnv
::get
('product') ne 'pve';
325 my $default_mib = get
('total_memory') * $ZFS_ARC_SYSMEM_PERCENTAGE;
326 my $rounded_mib = int(sprintf('%.0f', $default_mib));
328 if ($rounded_mib > $ZFS_ARC_MAX_SIZE_MIB) {
329 return $ZFS_ARC_MAX_SIZE_MIB;
330 } elsif ($rounded_mib < $ZFS_ARC_MIN_SIZE_MIB) {
331 return $ZFS_ARC_MIN_SIZE_MIB;
337 # Clamps the provided ZFS arc_max value (in MiB) to the accepted bounds. The
338 # lower is specified by `$ZFS_ARC_MIN_SIZE_MIB`, the upper by the available system memory.
339 # Returns the clamped value in MiB.
340 sub clamp_zfs_arc_max
{
343 return $mib if $mib == 0;
345 my $total_mem_mib = get
('total_memory');
346 if ($mib > $total_mem_mib) {
347 return $total_mem_mib;
348 } elsif ($mib < $ZFS_ARC_MIN_SIZE_MIB) {
349 return $ZFS_ARC_MIN_SIZE_MIB;
358 $_env = query_installation_environment
() if !defined($_env);
359 return defined($k) ?
$_env->{$k} : $_env;