]>
Commit | Line | Data |
---|---|---|
2231d226 WB |
1 | package Proxmox::Install::RunEnv; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use Carp; | |
7 | use JSON qw(from_json to_json); | |
2231d226 | 8 | |
0d27321b | 9 | use Proxmox::Log; |
88adf315 | 10 | use Proxmox::Sys::File qw(file_read_firstline); |
ceec3def | 11 | use Proxmox::Sys::Block; |
fe06d7e9 | 12 | use Proxmox::Sys::Net; |
2231d226 | 13 | |
e1b74696 TL |
14 | use Proxmox::Install::ISOEnv; |
15 | ||
2231d226 WB |
16 | my sub fromjs : prototype($) { |
17 | return from_json($_[0], { utf8 => 1 }); | |
18 | } | |
19 | ||
b91f9cad | 20 | my $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 |
23 | sub 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 |
40 | my $cpu_hvm_support = undef; |
41 | sub 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 | # } | |
73 | my 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 | # } | |
127 | my 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 | # | |
165 | my 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. | |
187 | my 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 | # } |
244 | sub 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 | |
310 | our $ZFS_ARC_MIN_SIZE_MIB = 64; # MiB | |
311 | ||
312 | # See https://bugzilla.proxmox.com/show_bug.cgi?id=4829 | |
313 | our $ZFS_ARC_MAX_SIZE_MIB = 16 * 1024; # 16384 MiB = 16 GiB | |
314 | our $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. | |
318 | sub 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. | |
337 | sub 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 |
352 | my $_env = undef; |
353 | sub get { | |
354 | my ($k) = @_; | |
355 | $_env = query_installation_environment() if !defined($_env); | |
356 | return defined($k) ? $_env->{$k} : $_env; | |
357 | } | |
358 | ||
2231d226 | 359 | 1; |