]> git.proxmox.com Git - pve-installer.git/blame - Proxmox/Install/RunEnv.pm
sys: command: add option to not print process output to stdout
[pve-installer.git] / Proxmox / Install / RunEnv.pm
CommitLineData
2231d226
WB
1package Proxmox::Install::RunEnv;
2
3use strict;
4use warnings;
5
6use Carp;
7use JSON qw(from_json to_json);
2231d226 8
0d27321b 9use Proxmox::Log;
88adf315 10use Proxmox::Sys::File qw(file_read_firstline);
ceec3def 11use Proxmox::Sys::Block;
fe06d7e9 12use Proxmox::Sys::Net;
2231d226 13
e1b74696
TL
14use Proxmox::Install::ISOEnv;
15
2231d226
WB
16my sub fromjs : prototype($) {
17 return from_json($_[0], { utf8 => 1 });
18}
19
b91f9cad 20my $mem_total = undef;
ed2b90a4
CH
21# Returns the system memory size in MiB, and falls back to 512 MiB if it
22# could not be determined.
b91f9cad
TL
23sub query_total_memory : prototype() {
24 return $mem_total if defined($mem_total);
25
26 open (my $MEMINFO, '<', '/proc/meminfo');
27
28 my $res = 512; # default to 512 if something goes wrong
29 while (my $line = <$MEMINFO>) {
30 if ($line =~ m/^MemTotal:\s+(\d+)\s*kB/i) {
31 $res = int ($1 / 1024);
32 }
33 }
34 close($MEMINFO);
35
36 $mem_total = $res;
37 return $mem_total;
38}
39
4d8aec8a
CH
40my $cpu_hvm_support = undef;
41sub query_cpu_hvm_support : prototype() {
42 return $cpu_hvm_support if defined($cpu_hvm_support);
43
44 open (my $CPUINFO, '<', '/proc/cpuinfo');
45
46 my $res = 0;
47 while (my $line = <$CPUINFO>) {
48 if ($line =~ /^flags\s*:.*(vmx|svm)/m) {
49 $res = 1;
50 last;
51 }
52 }
53 close($CPUINFO);
54
55 $cpu_hvm_support = $res;
56 return $cpu_hvm_support;
57}
58
2231d226
WB
59# Returns a hash.
60#
61# {
62# <ifname> => {
63# mac => <mac address>,
64# index => <index>,
65# name => <ifname>,
66# addresses => [
67# family => <inet|inet6>,
68# address => <mac address>,
69# prefix => <length>,
70# ],
71# },
72# }
73my sub query_netdevs : prototype() {
74 my $ifs = {};
75 my $default;
76
88adf315 77 # FIXME: not the same as the battle proven way we used in the installer for years?
2231d226
WB
78 my $interfaces = fromjs(qx/ip --json address show/);
79
80 for my $if (@$interfaces) {
81 my ($index, $name, $state, $mac, $addresses) =
82 $if->@{qw(ifindex ifname operstate address addr_info)};
83
5ea718f6 84 next if !$name || $name eq 'lo'; # could also check flags for LOOPBACK..
2231d226
WB
85
86 my @valid_addrs;
5ea718f6
TL
87 if (uc($state) eq 'UP') {
88 for my $addr (@$addresses) {
89 next if $addr->{scope} eq 'link';
90
91 my ($family, $addr, $prefix) = $addr->@{qw(family local prefixlen)};
92
93 push @valid_addrs, {
94 family => $family,
95 address => $addr,
96 prefix => $prefix,
97 };
98 }
2231d226
WB
99 }
100
5ea718f6
TL
101 $ifs->{$name} = {
102 index => $index,
103 name => $name,
104 mac => $mac,
5c450165 105 state => uc($state),
5ea718f6
TL
106 };
107 $ifs->{$name}->{addresses} = \@valid_addrs if @valid_addrs;
2231d226
WB
108 }
109
110 return $ifs;
111}
112
113# Returns a hash.
114#
115# {
116# gateway4 => {
117# dst => "default",
118# gateway => <ipv4>,
119# dev => <ifname>,
120# },
121# gateway6 => {
122# dst => "default",
123# gateway => <ipv6>,
124# dev => <ifname>,
125# },
126# }
127my sub query_routes : prototype() {
128 my ($gateway4, $gateway6);
129
a467ec47 130 log_info("query routes");
2231d226
WB
131 my $route4 = fromjs(qx/ip -4 --json route show/);
132 for my $route (@$route4) {
133 if ($route->{dst} eq 'default') {
134 $gateway4 = {
135 dev => $route->{dev},
136 gateway => $route->{gateway},
137 };
138 last;
139 }
140 }
141
142 my $route6 = fromjs(qx/ip -6 --json route show/);
143 for my $route (@$route6) {
144 if ($route->{dst} eq 'default') {
145 $gateway6 = {
146 dev => $route->{dev},
147 gateway => $route->{gateway},
148 };
149 last;
150 }
151 }
152
153 my $routes;
154 $routes->{gateway4} = $gateway4 if $gateway4;
155 $routes->{gateway6} = $gateway6 if $gateway6;
156
157 return $routes;
158}
159
160# If `/etc/resolv.conf` fails to open this returns nothing.
161# Otherwise it returns a hash:
162# {
163# dns => <first dns entry>,
164#
165my sub query_dns : prototype() {
a467ec47 166 log_info("query DNS from resolv.conf (managed by DHCP client)");
2231d226
WB
167 open my $fh , '<', '/etc/resolv.conf' or return;
168
169 my @dns;
170 my $domain;
171 while (defined(my $line = <$fh>)) {
172 if ($line =~ /^nameserver\s+(\S+)/) {
173 push @dns, $1;
174 } elsif (!defined($domain) && $line =~ /^domain\s+(\S+)/) {
175 $domain = $1;
176 }
177 }
178
179 my $output = {
180 domain => $domain,
181 @dns ? (dns => \@dns) : (),
182 };
183};
184
185# Uses `traceroute` and `geoiplookup`/`geoiplookup6` to figure out the current country.
186# Has a 10s timeout and uses the stops at the first entry found in the geoip database.
187my sub detect_country_tracing_to : prototype($$) {
188 my ($ipver, $destination) = @_;
189
a467ec47
TL
190 print STDERR "trying to detect country...\n";
191 open(my $TRACEROUTE_FH, '-|', 'traceroute', "-$ipver", '-N', '1', '-q', '1', '-n', $destination)
2231d226
WB
192 or return undef;
193
194 my $geoip_bin = ($ipver == 6) ? 'geoiplookup6' : 'geoiplookup';
195
196 my $country;
197
198 my $previous_alarm = alarm (10);
199 eval {
200 local $SIG{ALRM} = sub { die "timed out!\n" };
201 my $line;
202 while (defined ($line = <$TRACEROUTE_FH>)) {
203 log_debug("DC TRACEROUTE: $line");
204 if ($line =~ m/^\s*\d+\s+(\S+)\s/) {
205 my $geoip = qx/$geoip_bin $1/;
206 log_debug("DC GEOIP: $geoip");
207 if ($geoip =~ m/GeoIP Country Edition:\s*([A-Z]+),/) {
208 $country = lc ($1);
209 log_info("DC FOUND: $country\n");
210 last;
211 }
212 }
213 }
214 };
215 my $err = $@;
216 alarm ($previous_alarm);
217
218 close($TRACEROUTE_FH);
219
220 if ($err) {
221 die "unable to detect country - $err\n";
222 } elsif ($country) {
a467ec47 223 print STDERR "detected country: " . uc($country) . "\n";
2231d226
WB
224 }
225
226 return $country;
227}
228
229# Returns the entire environment as a hash.
230# {
231# country => <short country>,
4d8aec8a
CH
232# ipconf = <see Proxmox::Sys::Net::get_ip_config()>,
233# kernel_cmdline = <contents of /proc/cmdline>,
234# total_memory = <memory size in MiB>,
235# hvm_supported = <1 if the CPU supports hardware-accelerated virtualization>,
236# boot_type = <either 'efi' or 'bios'>,
ceec3def 237# disks => <see Proxmox::Sys::Block::hd_list()>,
2231d226
WB
238# network => {
239# interfaces => <see query_netdevs()>,
240# routes => <see query_routes()>,
1c5e103a 241# dns => <see query_dns()>,
2231d226 242# },
2231d226
WB
243# }
244sub query_installation_environment : prototype() {
e1b74696
TL
245 # check first if somebody already cached this for us and re-use that
246 my $run_env_file = Proxmox::Install::ISOEnv::get('run-env-cache-file');
247 if (-f "$run_env_file") {
248 log_info("re-using cached runtime env from $run_env_file");
249 my $cached_env = eval {
250 my $run_env_raw = Proxmox::Sys::File::file_read_all($run_env_file);
251 return fromjs($run_env_raw); # returns from eval
252 };
253 log_error("failed to parse cached runtime env - $@") if $@;
254 return $cached_env if defined($cached_env) && scalar keys $cached_env->%*;
255 log_warn("cached runtime env seems empty, query everything (again)");
256 }
257 # else re-query everything
2231d226
WB
258 my $output = {};
259
260 my $routes = query_routes();
261
a467ec47 262 log_info("query block devices");
ceec3def 263 $output->{disks} = Proxmox::Sys::Block::get_cached_disks();
2231d226
WB
264 $output->{network} = {
265 interfaces => query_netdevs(),
266 routes => $routes,
1c5e103a 267 dns => query_dns(),
2231d226 268 };
bda1cdf6 269
5130de4d
TL
270 # avoid serializing out null or an empty string, that can trip up the UIs
271 if (my $fqdn = Proxmox::Sys::Net::get_dhcp_fqdn()) {
272 $output->{network}->{hostname} = $fqdn;
a8054233 273 }
bda1cdf6 274
fe06d7e9
TL
275 # FIXME: move whatever makes sense over to Proxmox::Sys::Net:: and keep that as single source,
276 # it can then use some different structure just fine (after adapting the GTK GUI to that) but
277 # **never** to (slightly different!) things for the same stuff...
278 $output->{ipconf} = Proxmox::Sys::Net::get_ip_config();
2231d226 279
88adf315 280 $output->{kernel_cmdline} = file_read_firstline("/proc/cmdline");
b91f9cad 281 $output->{total_memory} = query_total_memory();
4d8aec8a 282 $output->{hvm_supported} = query_cpu_hvm_support();
71583761 283 $output->{boot_type} = -d '/sys/firmware/efi' ? 'efi' : 'bios';
b91f9cad 284
2231d226
WB
285 my $err;
286 my $country;
287 if ($routes->{gateway4}) {
a467ec47 288 log_info("trace country via IPv4");
2231d226
WB
289 $country = eval { detect_country_tracing_to(4 => '8.8.8.8') };
290 $err = $@ if !$country;
291 }
292
293 if (!$country && $routes->{gateway6}) {
a467ec47 294 log_info("trace country via IPv6");
2231d226
WB
295 $country = eval { detect_country_tracing_to(6 => '2001:4860:4860::8888') };
296 $err = $@ if !$country;
297 }
298
299 if (defined($country)) {
300 $output->{country} = $country;
301 } else {
302 warn ($err // "unable to detect country\n");
303 }
304
305 return $output;
306}
307
42aa2fa7
CH
308# OpenZFS specifies 64 MiB as the absolute minimum:
309# https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max
310our $ZFS_ARC_MIN_SIZE_MIB = 64; # MiB
311
312# See https://bugzilla.proxmox.com/show_bug.cgi?id=4829
313our $ZFS_ARC_MAX_SIZE_MIB = 16 * 1024; # 16384 MiB = 16 GiB
314our $ZFS_ARC_SYSMEM_PERCENTAGE = 0.1; # use 10% of available system memory by default
315
316# Calculates the default upper limit for the ZFS ARC size.
317# Returns the default ZFS maximum ARC size in MiB.
318sub default_zfs_arc_max {
319 # Use ZFS default on non-PVE
320 return 0 if Proxmox::Install::ISOEnv::get('product') ne 'pve';
321
322 my $default_mib = get('total_memory') * $ZFS_ARC_SYSMEM_PERCENTAGE;
323 my $rounded_mib = int(sprintf('%.0f', $default_mib));
42aa2fa7
CH
324
325 if ($rounded_mib > $ZFS_ARC_MAX_SIZE_MIB) {
326 return $ZFS_ARC_MAX_SIZE_MIB;
327 } elsif ($rounded_mib < $ZFS_ARC_MIN_SIZE_MIB) {
328 return $ZFS_ARC_MIN_SIZE_MIB;
329 }
330
331 return $rounded_mib;
332}
333
334# Clamps the provided ZFS arc_max value (in MiB) to the accepted bounds. The
335# lower is specified by `$ZFS_ARC_MIN_SIZE_MIB`, the upper by the available system memory.
336# Returns the clamped value in MiB.
337sub clamp_zfs_arc_max {
338 my ($mib) = @_;
339
340 return $mib if $mib == 0;
341
342 my $total_mem_mib = get('total_memory');
343 if ($mib > $total_mem_mib) {
344 return $total_mem_mib;
345 } elsif ($mib < $ZFS_ARC_MIN_SIZE_MIB) {
346 return $ZFS_ARC_MIN_SIZE_MIB;
347 }
348
349 return $mib;
350}
351
ea3b7cae
TL
352my $_env = undef;
353sub get {
354 my ($k) = @_;
355 $_env = query_installation_environment() if !defined($_env);
356 return defined($k) ? $_env->{$k} : $_env;
357}
358
2231d226 3591;