]> git.proxmox.com Git - pve-installer.git/blob - Proxmox/Install/RunEnv.pm
run env: add comment for query_total_memory()
[pve-installer.git] / Proxmox / Install / RunEnv.pm
1 package Proxmox::Install::RunEnv;
2
3 use strict;
4 use warnings;
5
6 use Carp;
7 use JSON qw(from_json to_json);
8
9 use Proxmox::Log;
10 use Proxmox::Sys::File qw(file_read_firstline);
11 use Proxmox::Sys::Block;
12 use Proxmox::Sys::Net;
13
14 use Proxmox::Install::ISOEnv;
15
16 my sub fromjs : prototype($) {
17 return from_json($_[0], { utf8 => 1 });
18 }
19
20 my $mem_total = undef;
21 # Returns the system memory size in MiB, and falls back to 512 MiB if it
22 # could not be determined.
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
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
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
77 # FIXME: not the same as the battle proven way we used in the installer for years?
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
84 next if !$name || $name eq 'lo'; # could also check flags for LOOPBACK..
85
86 my @valid_addrs;
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 }
99 }
100
101 $ifs->{$name} = {
102 index => $index,
103 name => $name,
104 mac => $mac,
105 state => uc($state),
106 };
107 $ifs->{$name}->{addresses} = \@valid_addrs if @valid_addrs;
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
130 log_info("query routes");
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() {
166 log_info("query DNS from resolv.conf (managed by DHCP client)");
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
190 print STDERR "trying to detect country...\n";
191 open(my $TRACEROUTE_FH, '-|', 'traceroute', "-$ipver", '-N', '1', '-q', '1', '-n', $destination)
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) {
223 print STDERR "detected country: " . uc($country) . "\n";
224 }
225
226 return $country;
227 }
228
229 # Returns the entire environment as a hash.
230 # {
231 # country => <short country>,
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'>,
237 # disks => <see Proxmox::Sys::Block::hd_list()>,
238 # network => {
239 # interfaces => <see query_netdevs()>,
240 # routes => <see query_routes()>,
241 # dns => <see query_dns()>,
242 # },
243 # }
244 sub query_installation_environment : prototype() {
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
258 my $output = {};
259
260 my $routes = query_routes();
261
262 log_info("query block devices");
263 $output->{disks} = Proxmox::Sys::Block::get_cached_disks();
264 $output->{network} = {
265 interfaces => query_netdevs(),
266 routes => $routes,
267 dns => query_dns(),
268 };
269
270 # Cannot be put directly in the above hash as it might return undef ..
271 $output->{network}->{hostname} = Proxmox::Sys::Net::get_dhcp_hostname();
272
273 # FIXME: move whatever makes sense over to Proxmox::Sys::Net:: and keep that as single source,
274 # it can then use some different structure just fine (after adapting the GTK GUI to that) but
275 # **never** to (slightly different!) things for the same stuff...
276 $output->{ipconf} = Proxmox::Sys::Net::get_ip_config();
277
278 $output->{kernel_cmdline} = file_read_firstline("/proc/cmdline");
279 $output->{total_memory} = query_total_memory();
280 $output->{hvm_supported} = query_cpu_hvm_support();
281 $output->{boot_type} = -d '/sys/firmware/efi' ? 'efi' : 'bios';
282
283 my $err;
284 my $country;
285 if ($routes->{gateway4}) {
286 log_info("trace country via IPv4");
287 $country = eval { detect_country_tracing_to(4 => '8.8.8.8') };
288 $err = $@ if !$country;
289 }
290
291 if (!$country && $routes->{gateway6}) {
292 log_info("trace country via IPv6");
293 $country = eval { detect_country_tracing_to(6 => '2001:4860:4860::8888') };
294 $err = $@ if !$country;
295 }
296
297 if (defined($country)) {
298 $output->{country} = $country;
299 } else {
300 warn ($err // "unable to detect country\n");
301 }
302
303 return $output;
304 }
305
306 my $_env = undef;
307 sub get {
308 my ($k) = @_;
309 $_env = query_installation_environment() if !defined($_env);
310 return defined($k) ? $_env->{$k} : $_env;
311 }
312
313 1;