]> git.proxmox.com Git - pve-installer.git/blame - Proxmox/Install/RunEnv.pm
add Proxmox::Install::RunEnv module
[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);
8use Proxmox::Log;
9
10use base qw(Exporter);
11our @EXPORT = qw(is_test_mode);
12
13my sub fromjs : prototype($) {
14 return from_json($_[0], { utf8 => 1 });
15}
16
17# Returns a hash.
18# {
19# name => {
20# size => <bytes>,
21# }
22# }
23my sub query_blockdevs : prototype() {
24 my $disks = {};
25
26 my $lsblk = fromjs(qx/lsblk --bytes --json/);
27 for my $disk ($lsblk->{blockdevices}->@*) {
28 my ($name, $ro, $size, $type, $mountpoints) = $disk->@{qw(name ro size type mountpoints)};
29
30 next if $type ne 'disk' || $ro;
31 next if grep { defined($_) } @$mountpoints;
32
33 $disks->{$name} = { size => $size };
34 }
35
36 return $disks;
37}
38
39# Returns a hash.
40#
41# {
42# <ifname> => {
43# mac => <mac address>,
44# index => <index>,
45# name => <ifname>,
46# addresses => [
47# family => <inet|inet6>,
48# address => <mac address>,
49# prefix => <length>,
50# ],
51# },
52# }
53my sub query_netdevs : prototype() {
54 my $ifs = {};
55 my $default;
56
57 my $interfaces = fromjs(qx/ip --json address show/);
58
59 for my $if (@$interfaces) {
60 my ($index, $name, $state, $mac, $addresses) =
61 $if->@{qw(ifindex ifname operstate address addr_info)};
62
63 next if $state ne 'UP';
64
65 my @valid_addrs;
66 for my $addr (@$addresses) {
67 next if $addr->{scope} eq 'link';
68
69 my ($family, $addr, $prefix) = $addr->@{qw(family local prefixlen)};
70
71 push @valid_addrs, {
72 family => $family,
73 address => $addr,
74 prefix => $prefix,
75 };
76 }
77
78 if (@valid_addrs) {
79 $ifs->{$name} = {
80 index => $index,
81 name => $name,
82 mac => $mac,
83 addresses => \@valid_addrs,
84 };
85 }
86 }
87
88 return $ifs;
89}
90
91# Returns a hash.
92#
93# {
94# gateway4 => {
95# dst => "default",
96# gateway => <ipv4>,
97# dev => <ifname>,
98# },
99# gateway6 => {
100# dst => "default",
101# gateway => <ipv6>,
102# dev => <ifname>,
103# },
104# }
105my sub query_routes : prototype() {
106 my ($gateway4, $gateway6);
107
108 my $route4 = fromjs(qx/ip -4 --json route show/);
109 for my $route (@$route4) {
110 if ($route->{dst} eq 'default') {
111 $gateway4 = {
112 dev => $route->{dev},
113 gateway => $route->{gateway},
114 };
115 last;
116 }
117 }
118
119 my $route6 = fromjs(qx/ip -6 --json route show/);
120 for my $route (@$route6) {
121 if ($route->{dst} eq 'default') {
122 $gateway6 = {
123 dev => $route->{dev},
124 gateway => $route->{gateway},
125 };
126 last;
127 }
128 }
129
130 my $routes;
131 $routes->{gateway4} = $gateway4 if $gateway4;
132 $routes->{gateway6} = $gateway6 if $gateway6;
133
134 return $routes;
135}
136
137# If `/etc/resolv.conf` fails to open this returns nothing.
138# Otherwise it returns a hash:
139# {
140# dns => <first dns entry>,
141#
142my sub query_dns : prototype() {
143 open my $fh , '<', '/etc/resolv.conf' or return;
144
145 my @dns;
146 my $domain;
147 while (defined(my $line = <$fh>)) {
148 if ($line =~ /^nameserver\s+(\S+)/) {
149 push @dns, $1;
150 } elsif (!defined($domain) && $line =~ /^domain\s+(\S+)/) {
151 $domain = $1;
152 }
153 }
154
155 my $output = {
156 domain => $domain,
157 @dns ? (dns => \@dns) : (),
158 };
159};
160
161# Uses `traceroute` and `geoiplookup`/`geoiplookup6` to figure out the current country.
162# Has a 10s timeout and uses the stops at the first entry found in the geoip database.
163my sub detect_country_tracing_to : prototype($$) {
164 my ($ipver, $destination) = @_;
165
166 print "trying to detect country...\n";
167 open(my $TRACEROUTE_FH, '-|',
168 'traceroute', "-$ipver", '-N', '1', '-q', '1', '-n', $destination)
169 or return undef;
170
171 my $geoip_bin = ($ipver == 6) ? 'geoiplookup6' : 'geoiplookup';
172
173 my $country;
174
175 my $previous_alarm = alarm (10);
176 eval {
177 local $SIG{ALRM} = sub { die "timed out!\n" };
178 my $line;
179 while (defined ($line = <$TRACEROUTE_FH>)) {
180 log_debug("DC TRACEROUTE: $line");
181 if ($line =~ m/^\s*\d+\s+(\S+)\s/) {
182 my $geoip = qx/$geoip_bin $1/;
183 log_debug("DC GEOIP: $geoip");
184 if ($geoip =~ m/GeoIP Country Edition:\s*([A-Z]+),/) {
185 $country = lc ($1);
186 log_info("DC FOUND: $country\n");
187 last;
188 }
189 }
190 }
191 };
192 my $err = $@;
193 alarm ($previous_alarm);
194
195 close($TRACEROUTE_FH);
196
197 if ($err) {
198 die "unable to detect country - $err\n";
199 } elsif ($country) {
200 print "detected country: " . uc($country) . "\n";
201 }
202
203 return $country;
204}
205
206# Returns the entire environment as a hash.
207# {
208# country => <short country>,
209# disks => <see query_blockdevs()>,
210# network => {
211# interfaces => <see query_netdevs()>,
212# routes => <see query_routes()>,
213# },
214# dns => <see query_dns()>,
215# }
216sub query_installation_environment : prototype() {
217 my $output = {};
218
219 my $routes = query_routes();
220
221 $output->{disks} = query_blockdevs();
222 $output->{network} = {
223 interfaces => query_netdevs(),
224 routes => $routes,
225 };
226 $output->{dns} = query_dns();
227
228 my $err;
229 my $country;
230 if ($routes->{gateway4}) {
231 $country = eval { detect_country_tracing_to(4 => '8.8.8.8') };
232 $err = $@ if !$country;
233 }
234
235 if (!$country && $routes->{gateway6}) {
236 $country = eval { detect_country_tracing_to(6 => '2001:4860:4860::8888') };
237 $err = $@ if !$country;
238 }
239
240 if (defined($country)) {
241 $output->{country} = $country;
242 } else {
243 warn ($err // "unable to detect country\n");
244 }
245
246 return $output;
247}
248
2491;