]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Setup/Debian.pm
Move JSONFormat code to PVE::LXC::Config
[pve-container.git] / src / PVE / LXC / Setup / Debian.pm
1 package PVE::LXC::Setup::Debian;
2
3 use strict;
4 use warnings;
5 use Data::Dumper;
6 use PVE::Tools qw($IPV6RE);
7 use PVE::LXC;
8 use PVE::Network;
9 use File::Path;
10
11 use PVE::LXC::Setup::Base;
12
13 use base qw(PVE::LXC::Setup::Base);
14
15 sub new {
16 my ($class, $conf, $rootdir) = @_;
17
18 my $version = PVE::Tools::file_read_firstline("$rootdir/etc/debian_version");
19
20 die "unable to read version info\n" if !defined($version);
21
22 # translate stretch/sid => 9.0 (used on debian testing repository)
23 $version = 9.0 if $version eq 'stretch/sid';
24
25 die "unable to parse version info '$version'\n"
26 if $version !~ m/^(\d+(\.\d+)?)(\.\d+)?/;
27
28 $version = $1;
29
30 die "unsupported debian version '$version'\n"
31 if !($version >= 4 && $version <= 9);
32
33 my $self = { conf => $conf, rootdir => $rootdir, version => $version };
34
35 $conf->{ostype} = "debian";
36
37 return bless $self, $class;
38 }
39
40 sub setup_init {
41 my ($self, $conf) = @_;
42
43 my $filename = "/etc/inittab";
44 return if !$self->ct_file_exists($filename);
45
46 my $ttycount = PVE::LXC::Config->get_tty_count($conf);
47 my $inittab = $self->ct_file_get_contents($filename);
48
49 my @lines = grep {
50 # remove getty lines
51 !/^\s*\d+:\d+:[^:]*:.*getty/ &&
52 # remove power lines
53 !/^\s*p[fno0]:/
54 } split(/\n/, $inittab);
55
56 $inittab = join("\n", @lines) . "\n";
57
58 $inittab .= "p0::powerfail:/sbin/init 0\n";
59
60 my $version = $self->{version};
61 for (my $id = 1; $id <= $ttycount; $id++) {
62 next if $id == 7; # reserved for X11
63 my $levels = ($id == 1) ? '2345' : '23';
64 if ($version < 7) {
65 $inittab .= "$id:$levels:respawn:/sbin/getty -L 38400 tty$id\n";
66 } else {
67 $inittab .= "$id:$levels:respawn:/sbin/getty --noclear 38400 tty$id\n";
68 }
69 }
70
71 $self->ct_file_set_contents($filename, $inittab);
72 }
73
74 sub remove_gateway_scripts {
75 my ($attr) = @_;
76 my $length = scalar(@$attr);
77 for (my $i = 0; $i < $length; ++$i) {
78 my $a = $attr->[$i];
79 if ($a =~ m@^\s*post-up\s+.*route.*add.*default.*(?:gw|via)\s+(\S+)@) {
80 my $gw = $1;
81 if ($i > 0 && $attr->[$i-1] =~ m@^\s*post-up\s+.*route.*add.*\Q$1\E@) {
82 --$i;
83 splice @$attr, $i, 2;
84 $length -= 2;
85 } else {
86 splice @$attr, $i, 1;
87 $length -= 1;
88 }
89 --$i;
90 next;
91 }
92 if ($a =~ m@^\s*pre-down\s+.*route.*del.*default.*(?:gw|via)\s+(\S+)@) {
93 my $gw = $1;
94 if ($attr->[$i+1] =~ m@^\s*pre-down\s+.*route.*del.*\Q$1\E@) {
95 splice @$attr, $i, 2;
96 $length -= 2;
97 } else {
98 splice @$attr, $i, 1;
99 $length -= 1;
100 }
101 --$i;
102 next;
103 }
104 }
105 }
106
107 sub make_gateway_scripts {
108 my ($ifname, $gw) = @_;
109 return <<"SCRIPTS";
110 \tpost-up ip route add $gw dev $ifname
111 \tpost-up ip route add default via $gw
112 \tpre-down ip route del default via $gw
113 \tpre-down ip route del $gw dev $ifname
114 SCRIPTS
115 }
116
117 sub setup_network {
118 my ($self, $conf) = @_;
119
120 my $networks = {};
121 foreach my $k (keys %$conf) {
122 next if $k !~ m/^net(\d+)$/;
123 my $ind = $1;
124 my $d = PVE::LXC::Config->parse_lxc_network($conf->{$k});
125 if ($d->{name}) {
126 my $net = {};
127 my $cidr;
128 if (defined($d->{ip})) {
129 if ($d->{ip} =~ /^(?:dhcp|manual)$/) {
130 $net->{address} = $d->{ip};
131 } else {
132 my $ipinfo = PVE::LXC::parse_ipv4_cidr($d->{ip});
133 $net->{address} = $ipinfo->{address};
134 $net->{netmask} = $ipinfo->{netmask};
135 $cidr = $d->{ip};
136 }
137 }
138 if (defined($d->{'gw'})) {
139 $net->{gateway} = $d->{'gw'};
140 if (defined($cidr) && !PVE::Network::is_ip_in_cidr($d->{gw}, $cidr, 4)) {
141 # gateway is not reachable, need an extra route
142 $net->{needsroute} = 1;
143 }
144 }
145 $cidr = undef;
146 if (defined($d->{ip6})) {
147 if ($d->{ip6} =~ /^(?:auto|dhcp|manual)$/) {
148 $net->{address6} = $d->{ip6};
149 } elsif ($d->{ip6} !~ /^($IPV6RE)\/(\d+)$/) {
150 die "unable to parse ipv6 address/prefix\n";
151 } else {
152 $net->{address6} = $1;
153 $net->{netmask6} = $2;
154 $cidr = $d->{ip6};
155 }
156 }
157 if (defined($d->{'gw6'})) {
158 $net->{gateway6} = $d->{'gw6'};
159 if (defined($cidr) && !PVE::Network::is_ip_in_cidr($d->{gw6}, $cidr, 6)) {
160 # gateway is not reachable, need an extra route
161 $net->{needsroute6} = 1;
162 }
163 }
164 $networks->{$d->{name}} = $net if keys %$net;
165 }
166 }
167
168 return if !scalar(keys %$networks);
169
170 my $filename = "/etc/network/interfaces";
171 my $interfaces = "";
172
173 my $section;
174
175 my $done_auto = {};
176 my $done_v4_hash = {};
177 my $done_v6_hash = {};
178
179 my $print_section = sub {
180 return if !$section;
181
182 my $ifname = $section->{ifname};
183 my $net = $networks->{$ifname};
184
185 if (!$done_auto->{$ifname}) {
186 $interfaces .= "auto $ifname\n";
187 $done_auto->{$ifname} = 1;
188 }
189
190 if ($section->{type} eq 'ipv4') {
191 $done_v4_hash->{$ifname} = 1;
192
193 if ($net->{address} =~ /^(dhcp|manual)$/) {
194 $interfaces .= "iface $ifname inet $1\n";
195 } else {
196 $interfaces .= "iface $ifname inet static\n";
197 $interfaces .= "\taddress $net->{address}\n" if defined($net->{address});
198 $interfaces .= "\tnetmask $net->{netmask}\n" if defined($net->{netmask});
199 if (defined(my $gw = $net->{gateway})) {
200 remove_gateway_scripts($section->{attr});
201 if ($net->{needsroute}) {
202 $interfaces .= make_gateway_scripts($ifname, $gw);
203 } else {
204 $interfaces .= "\tgateway $gw\n";
205 }
206 }
207 foreach my $attr (@{$section->{attr}}) {
208 $interfaces .= "\t$attr\n";
209 }
210 }
211
212 $interfaces .= "\n";
213
214 } elsif ($section->{type} eq 'ipv6') {
215 $done_v6_hash->{$ifname} = 1;
216
217 if ($net->{address6} =~ /^(auto|dhcp|manual)$/) {
218 $interfaces .= "iface $ifname inet6 $1\n";
219 } else {
220 $interfaces .= "iface $ifname inet6 static\n";
221 $interfaces .= "\taddress $net->{address6}\n" if defined($net->{address6});
222 $interfaces .= "\tnetmask $net->{netmask6}\n" if defined($net->{netmask6});
223 if (defined(my $gw = $net->{gateway6})) {
224 remove_gateway_scripts($section->{attr});
225 if ($net->{needsroute6}) {
226 $interfaces .= make_gateway_scripts($ifname, $gw);
227 } else {
228 $interfaces .= "\tgateway $net->{gateway6}\n" if defined($net->{gateway6});
229 }
230 }
231 foreach my $attr (@{$section->{attr}}) {
232 $interfaces .= "\t$attr\n";
233 }
234 }
235
236 $interfaces .= "\n";
237 } else {
238 die "unknown section type '$section->{type}'";
239 }
240
241 $section = undef;
242 };
243
244 if (my $fh = $self->ct_open_file_read($filename)) {
245 while (defined (my $line = <$fh>)) {
246 chomp $line;
247 if ($line =~ m/^#/) {
248 $interfaces .= "$line\n";
249 next;
250 }
251 if ($line =~ m/^\s*$/) {
252 if ($section) {
253 &$print_section();
254 } else {
255 $interfaces .= "$line\n";
256 }
257 next;
258 }
259 if ($line =~ m/^\s*iface\s+(\S+)\s+inet\s+(\S+)\s*$/) {
260 my $ifname = $1;
261 &$print_section(); # print previous section
262 if (!$networks->{$ifname}) {
263 $interfaces .= "$line\n";
264 next;
265 }
266 $section = { type => 'ipv4', ifname => $ifname, attr => []};
267 next;
268 }
269 if ($line =~ m/^\s*iface\s+(\S+)\s+inet6\s+(\S+)\s*$/) {
270 my $ifname = $1;
271 &$print_section(); # print previous section
272 if (!$networks->{$ifname}) {
273 $interfaces .= "$line\n";
274 next;
275 }
276 $section = { type => 'ipv6', ifname => $ifname, attr => []};
277 next;
278 }
279 # Handle 'auto'
280 if ($line =~ m/^\s*auto\s*(.*)$/) {
281 foreach my $iface (split(/\s+/, $1)) {
282 $done_auto->{$iface} = 1;
283 }
284 &$print_section();
285 $interfaces .= "$line\n";
286 next;
287 }
288 # Handle other section delimiters:
289 if ($line =~ m/^\s*(?:mapping\s
290 |allow-
291 |source\s
292 |source-directory\s
293 )/x) {
294 &$print_section();
295 $interfaces .= "$line\n";
296 next;
297 }
298 if ($section && $line =~ m/^\s*((\S+)\s(.*))$/) {
299 my ($adata, $aname) = ($1, $2);
300 if ($aname eq 'address' || $aname eq 'netmask' ||
301 $aname eq 'gateway' || $aname eq 'broadcast') {
302 # skip
303 } else {
304 push @{$section->{attr}}, $adata;
305 }
306 next;
307 }
308
309 $interfaces .= "$line\n";
310 }
311 &$print_section();
312 }
313
314 my $need_separator = length($interfaces) && ($interfaces !~ /\n\n$/);
315 foreach my $ifname (sort keys %$networks) {
316 my $net = $networks->{$ifname};
317
318 if (!$done_v4_hash->{$ifname} && defined($net->{address})) {
319 if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };
320 $section = { type => 'ipv4', ifname => $ifname, attr => []};
321 &$print_section();
322 }
323 if (!$done_v6_hash->{$ifname} && defined($net->{address6})) {
324 if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };
325 $section = { type => 'ipv6', ifname => $ifname, attr => []};
326 &$print_section();
327 }
328 }
329
330 $self->ct_file_set_contents($filename, $interfaces);
331 }
332
333 1;