]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Setup/Debian.pm
add support for debian bullseye/sid
[pve-container.git] / src / PVE / LXC / Setup / Debian.pm
1 package PVE::LXC::Setup::Debian;
2
3 use strict;
4 use warnings;
5
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 testing version names
23 $version = 9.1 if $version eq 'stretch/sid';
24 $version = 10 if $version eq 'buster/sid';
25 $version = 11 if $version eq 'bullseye/sid';
26
27 die "unable to parse version info '$version'\n"
28 if $version !~ m/^(\d+(\.\d+)?)(\.\d+)?/;
29
30 $version = $1;
31
32 die "unsupported debian version '$version'\n"
33 if !($version >= 4 && $version <= 11);
34
35 my $self = { conf => $conf, rootdir => $rootdir, version => $version };
36
37 $conf->{ostype} = "debian";
38
39 return bless $self, $class;
40 }
41
42 # Debian doesn't support the /dev/lxc/ subdirectory.
43 sub devttydir {
44 return '';
45 }
46
47 sub setup_init {
48 my ($self, $conf) = @_;
49
50 my $systemd = $self->ct_readlink('/sbin/init');
51 if (defined($systemd) && $systemd =~ m@/systemd$@) {
52 $self->setup_container_getty_service($conf);
53 }
54
55 my $filename = "/etc/inittab";
56 return if !$self->ct_file_exists($filename);
57
58 my $ttycount = PVE::LXC::Config->get_tty_count($conf);
59 my $inittab = $self->ct_file_get_contents($filename);
60
61 my @lines = grep {
62 # remove getty lines
63 !/^\s*\d+:\d*:[^:]*:.*getty/ &&
64 # remove power lines
65 !/^\s*p[fno0]:/
66 } split(/\n/, $inittab);
67
68 $inittab = join("\n", @lines) . "\n";
69
70 $inittab .= "p0::powerfail:/sbin/init 0\n";
71
72 my $version = $self->{version};
73 for (my $id = 1; $id <= $ttycount; $id++) {
74 next if $id == 7; # reserved for X11
75 my $levels = ($id == 1) ? '2345' : '23';
76 if ($version < 7) {
77 $inittab .= "$id:$levels:respawn:/sbin/getty -L 38400 tty$id\n";
78 } else {
79 $inittab .= "$id:$levels:respawn:/sbin/getty --noclear 38400 tty$id\n";
80 }
81 }
82
83 $self->ct_file_set_contents($filename, $inittab);
84 }
85
86 sub remove_gateway_scripts {
87 my ($attr) = @_;
88 my $length = scalar(@$attr);
89
90 my $found_section = 0;
91 my $keep = 1;
92 @$attr = grep {
93 if ($_ eq '# --- BEGIN PVE ---') {
94 $found_section = 1;
95 $keep = 0;
96 0; # remove this line
97 } elsif ($_ eq '# --- END PVE ---') {
98 $found_section = 1;
99 $keep = 1;
100 0; # remove this line
101 } else {
102 $keep;
103 }
104 } @$attr;
105
106 return if $found_section;
107 # XXX: To deal with existing setups we perform two types of removal for
108 # now. Newly started containers have their routing sections marked with
109 # begin/end comments. For older containers we perform a strict matching on
110 # the routing rules we added. We can probably remove this part at some point
111 # when it is unlikely that old debian setups are still around.
112
113 for (my $i = 0; $i < $length-3; ++$i) {
114 next if $attr->[$i+0] !~ m@^\s*post-up\s+ip\s+route\s+add\s+(\S+)\s+dev\s+(\S+)$@;
115 my ($ip, $dev) = ($1, $2);
116 if ($attr->[$i+1] =~ m@^\s*post-up\s+ip\s+route\s+add\s+default\s+via\s+(\S+)\s+dev\s+(\S+)$@ &&
117 ($ip eq $1 && $dev eq $2) &&
118 $attr->[$i+2] =~ m@^\s*pre-down\s+ip\s+route\s+del\s+default\s+via\s+(\S+)\s+dev\s+(\S+)$@ &&
119 ($ip eq $1 && $dev eq $2) &&
120 $attr->[$i+3] =~ m@^\s*pre-down\s+ip\s+route\s+del\s+(\S+)\s+dev\s+(\S+)$@ &&
121 ($ip eq $1 && $dev eq $2))
122 {
123 splice @$attr, $i, 4;
124 $length -= 4;
125 --$i;
126 }
127 }
128 }
129
130 sub make_gateway_scripts {
131 my ($ifname, $gw) = @_;
132 return <<"SCRIPTS";
133 # --- BEGIN PVE ---
134 \tpost-up ip route add $gw dev $ifname
135 \tpost-up ip route add default via $gw dev $ifname
136 \tpre-down ip route del default via $gw dev $ifname
137 \tpre-down ip route del $gw dev $ifname
138 # --- END PVE ---
139 SCRIPTS
140 }
141
142 sub setup_network {
143 my ($self, $conf) = @_;
144
145 my $networks = {};
146 foreach my $k (keys %$conf) {
147 next if $k !~ m/^net(\d+)$/;
148 my $ind = $1;
149 my $d = PVE::LXC::Config->parse_lxc_network($conf->{$k});
150 if ($d->{name}) {
151 my $net = {};
152 my $cidr;
153 if (defined($d->{ip})) {
154 if ($d->{ip} =~ /^(?:dhcp|manual)$/) {
155 $net->{address} = $d->{ip};
156 } else {
157 my $ipinfo = PVE::LXC::parse_ipv4_cidr($d->{ip});
158 $net->{address} = $ipinfo->{address};
159 $net->{netmask} = $ipinfo->{netmask};
160 $cidr = $d->{ip};
161 }
162 }
163 if (defined($d->{'gw'})) {
164 $net->{gateway} = $d->{'gw'};
165 if (defined($cidr) && !PVE::Network::is_ip_in_cidr($d->{gw}, $cidr, 4)) {
166 # gateway is not reachable, need an extra route
167 $net->{needsroute} = 1;
168 }
169 }
170 $cidr = undef;
171 if (defined($d->{ip6})) {
172 if ($d->{ip6} =~ /^(?:auto|dhcp|manual)$/) {
173 $net->{address6} = $d->{ip6};
174 } elsif ($d->{ip6} !~ /^($IPV6RE)\/(\d+)$/) {
175 die "unable to parse ipv6 address/prefix\n";
176 } else {
177 $net->{address6} = $1;
178 $net->{netmask6} = $2;
179 $cidr = $d->{ip6};
180 }
181 }
182 if (defined($d->{'gw6'})) {
183 $net->{gateway6} = $d->{'gw6'};
184 if (defined($cidr) && !PVE::Network::is_ip_in_cidr($d->{gw6}, $cidr, 6) &&
185 !PVE::Network::is_ip_in_cidr($d->{gw6}, 'fe80::/10', 6)) {
186 # gateway is not reachable, need an extra route
187 $net->{needsroute6} = 1;
188 }
189 }
190 $networks->{$d->{name}} = $net if keys %$net;
191 }
192 }
193
194 return if !scalar(keys %$networks);
195
196 my $filename = "/etc/network/interfaces";
197 my $interfaces = "";
198
199 my $section;
200
201 my $done_auto = {};
202 my $done_v4_hash = {};
203 my $done_v6_hash = {};
204
205 my $print_section = sub {
206 return if !$section;
207
208 my $ifname = $section->{ifname};
209 my $net = $networks->{$ifname};
210
211 if (!$done_auto->{$ifname}) {
212 $interfaces .= "auto $ifname\n";
213 $done_auto->{$ifname} = 1;
214 }
215
216 if ($section->{type} eq 'ipv4') {
217 $done_v4_hash->{$ifname} = 1;
218
219 if (!defined($net->{address})) {
220 # no address => no iface line
221 } elsif ($net->{address} =~ /^(dhcp|manual)$/) {
222 $interfaces .= "iface $ifname inet $1\n\n";
223 } else {
224 $interfaces .= "iface $ifname inet static\n";
225 $interfaces .= "\taddress $net->{address}\n" if defined($net->{address});
226 $interfaces .= "\tnetmask $net->{netmask}\n" if defined($net->{netmask});
227
228 remove_gateway_scripts($section->{attr});
229 if (defined(my $gw = $net->{gateway})) {
230 if ($net->{needsroute}) {
231 $interfaces .= make_gateway_scripts($ifname, $gw);
232 } else {
233 $interfaces .= "\tgateway $gw\n";
234 }
235 }
236 foreach my $attr (@{$section->{attr}}) {
237 $interfaces .= "\t$attr\n";
238 }
239 $interfaces .= "\n";
240 }
241 } elsif ($section->{type} eq 'ipv6') {
242 $done_v6_hash->{$ifname} = 1;
243
244 if (!defined($net->{address6})) {
245 # no address => no iface line
246 } elsif ($net->{address6} =~ /^(auto|dhcp|manual)$/) {
247 $interfaces .= "iface $ifname inet6 $1\n\n";
248 } else {
249 $interfaces .= "iface $ifname inet6 static\n";
250 $interfaces .= "\taddress $net->{address6}\n" if defined($net->{address6});
251 $interfaces .= "\tnetmask $net->{netmask6}\n" if defined($net->{netmask6});
252 remove_gateway_scripts($section->{attr});
253 if (defined(my $gw = $net->{gateway6})) {
254 if ($net->{needsroute6}) {
255 $interfaces .= make_gateway_scripts($ifname, $gw);
256 } else {
257 $interfaces .= "\tgateway $net->{gateway6}\n" if defined($net->{gateway6});
258 }
259 }
260 foreach my $attr (@{$section->{attr}}) {
261 $interfaces .= "\t$attr\n";
262 }
263 $interfaces .= "\n";
264 }
265 } else {
266 die "unknown section type '$section->{type}'";
267 }
268
269 $section = undef;
270 };
271
272 if (my $fh = $self->ct_open_file_read($filename)) {
273 while (defined (my $line = <$fh>)) {
274 chomp $line;
275 if ($line =~ m/^# --- (?:BEGIN|END) PVE ---/) {
276 # Include markers in the attribute section so
277 # remove_gateway_scripts() can find them.
278 push @{$section->{attr}}, $line if $section;
279 next;
280 }
281 if ($line =~ m/^#/) {
282 $interfaces .= "$line\n";
283 next;
284 }
285 if ($line =~ m/^\s*$/) {
286 if ($section) {
287 &$print_section();
288 } else {
289 $interfaces .= "$line\n";
290 }
291 next;
292 }
293 if ($line =~ m/^\s*iface\s+(\S+)\s+inet\s+(\S+)\s*$/) {
294 my $ifname = $1;
295 &$print_section(); # print previous section
296 if (!$networks->{$ifname}) {
297 $interfaces .= "$line\n";
298 next;
299 }
300 $section = { type => 'ipv4', ifname => $ifname, attr => []};
301 next;
302 }
303 if ($line =~ m/^\s*iface\s+(\S+)\s+inet6\s+(\S+)\s*$/) {
304 my $ifname = $1;
305 &$print_section(); # print previous section
306 if (!$networks->{$ifname}) {
307 $interfaces .= "$line\n";
308 next;
309 }
310 $section = { type => 'ipv6', ifname => $ifname, attr => []};
311 next;
312 }
313 # Handle 'auto'
314 if ($line =~ m/^\s*auto\s*(.*)$/) {
315 foreach my $iface (split(/\s+/, $1)) {
316 $done_auto->{$iface} = 1;
317 }
318 &$print_section();
319 $interfaces .= "$line\n";
320 next;
321 }
322 # Handle other section delimiters:
323 if ($line =~ m/^\s*(?:mapping\s
324 |allow-
325 |source\s
326 |source-directory\s
327 )/x) {
328 &$print_section();
329 $interfaces .= "$line\n";
330 next;
331 }
332 if ($section && $line =~ m/^\s*((\S+)\s(.*))$/) {
333 my ($adata, $aname) = ($1, $2);
334 if ($aname eq 'address' || $aname eq 'netmask' ||
335 $aname eq 'gateway' || $aname eq 'broadcast') {
336 # skip
337 } else {
338 push @{$section->{attr}}, $adata;
339 }
340 next;
341 }
342
343 $interfaces .= "$line\n";
344 }
345 &$print_section();
346 }
347
348 my $need_separator = length($interfaces) && ($interfaces !~ /\n\n$/);
349 foreach my $ifname (sort keys %$networks) {
350 my $net = $networks->{$ifname};
351
352 if (!$done_v4_hash->{$ifname} && defined($net->{address})) {
353 if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };
354 $section = { type => 'ipv4', ifname => $ifname, attr => []};
355 &$print_section();
356 }
357 if (!$done_v6_hash->{$ifname} && defined($net->{address6})) {
358 if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };
359 $section = { type => 'ipv6', ifname => $ifname, attr => []};
360 &$print_section();
361 }
362 }
363
364 # older templates (< Debian 8) do not configure the loopback interface
365 # if not explicitly told to do so
366 if (!$done_auto->{lo}) {
367 $interfaces = "auto lo\niface lo inet loopback\n" .
368 "iface lo inet6 loopback\n\n" .
369 $interfaces;
370 }
371
372 $self->ct_file_set_contents($filename, $interfaces);
373 }
374
375 1;