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