]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC/Setup/Debian.pm
remove Data::Dumper usages
[pve-container.git] / src / PVE / LXC / Setup / Debian.pm
index ebfc97262faf33ae23efdc12000168bb47d2e194..50398892e512f695c79df4245c303caac618fe2b 100644 (file)
@@ -2,9 +2,10 @@ package PVE::LXC::Setup::Debian;
 
 use strict;
 use warnings;
-use Data::Dumper;
+
 use PVE::Tools qw($IPV6RE);
 use PVE::LXC;
+use PVE::Network;
 use File::Path;
 
 use PVE::LXC::Setup::Base;
@@ -18,13 +19,17 @@ sub new {
 
     die "unable to read version info\n" if !defined($version);
 
-    die "unable to parse version info\n"
+    # translate testing version names
+    $version = 9.1 if $version eq 'stretch/sid';
+    $version = 10 if $version eq 'buster/sid';
+
+    die "unable to parse version info '$version'\n"
        if $version !~ m/^(\d+(\.\d+)?)(\.\d+)?/;
 
     $version = $1;
 
-    die "unsupported debian version '$version'\n" 
-       if !($version >= 6 && $version < 9);
+    die "unsupported debian version '$version'\n"
+       if !($version >= 4 && $version <= 10);
 
     my $self = { conf => $conf, rootdir => $rootdir, version => $version };
 
@@ -33,70 +38,104 @@ sub new {
     return bless $self, $class;
 }
 
-my $default_inittab = <<__EOD__;
-
-# The default runlevel.
-id:2:initdefault:
-
-# Boot-time system configuration/initialization script.
-# This is run first except when booting in emergency (-b) mode.
-si::sysinit:/etc/init.d/rcS
-
-# /etc/init.d executes the S and K scripts upon change
-# of runlevel.
-#
-# Runlevel 0 is halt.
-# Runlevel 1 is single-user.
-# Runlevels 2-5 are multi-user.
-# Runlevel 6 is reboot.
-
-l0:0:wait:/etc/init.d/rc 0
-l1:1:wait:/etc/init.d/rc 1
-l2:2:wait:/etc/init.d/rc 2
-l3:3:wait:/etc/init.d/rc 3
-l4:4:wait:/etc/init.d/rc 4
-l5:5:wait:/etc/init.d/rc 5
-l6:6:wait:/etc/init.d/rc 6
-# Normally not reached, but fallthrough in case of emergency.
-z6:6:respawn:/sbin/sulogin
-
-# What to do when CTRL-ALT-DEL is pressed.
-ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now
-
-# What to do when the power fails/returns.
-p0::powerfail:/sbin/init 0
-
-# /sbin/getty invocations for the runlevels.
-#
-# The "id" field MUST be the same as the last
-# characters of the device (after "tty").
-#
-# Format:
-#  <id>:<runlevels>:<action>:<process>
-#
-__EOD__
+# Debian doesn't support the /dev/lxc/ subdirectory.
+sub devttydir {
+    return '';
+}
 
 sub setup_init {
     my ($self, $conf) = @_;
 
+    my $systemd = $self->ct_readlink('/sbin/init');
+    if (defined($systemd) && $systemd =~ m@/systemd$@) {
+       $self->setup_container_getty_service($conf);
+    }
+
     my $filename = "/etc/inittab";
+    return if !$self->ct_file_exists($filename);
 
-    if ($self->ct_file_exists($filename)) {
-       my $inittab = $default_inittab;
+    my $ttycount =  PVE::LXC::Config->get_tty_count($conf);
+    my $inittab = $self->ct_file_get_contents($filename);
 
-       my $ttycount =  PVE::LXC::get_tty_count($conf);
-       for (my $i = 1; $i <= $ttycount; $i++) {
-           next if $i == 7; # reserved for X11
-           my $levels = ($i == 1) ? '2345' : '23';
-           if ($self->{version} < 7) {
-               $inittab .= "$i:$levels:respawn:/sbin/getty -L 38400 tty$i\n";
-           } else {
-               $inittab .= "$i:$levels:respawn:/sbin/getty --noclear 38400 tty$i\n";
-           }
+    my @lines = grep {
+           # remove getty lines
+           !/^\s*\d+:\d*:[^:]*:.*getty/ &&
+           # remove power lines
+           !/^\s*p[fno0]:/
+       } split(/\n/, $inittab);
+
+    $inittab = join("\n", @lines) . "\n";
+
+    $inittab .= "p0::powerfail:/sbin/init 0\n";
+
+    my $version = $self->{version};
+    for (my $id = 1; $id <= $ttycount; $id++) {
+       next if $id == 7; # reserved for X11
+       my $levels = ($id == 1) ? '2345' : '23';
+       if ($version < 7) {
+           $inittab .= "$id:$levels:respawn:/sbin/getty -L 38400 tty$id\n";
+       } else {
+           $inittab .= "$id:$levels:respawn:/sbin/getty --noclear 38400 tty$id\n";
        }
-       
-       $self->ct_file_set_contents($filename, $inittab);
     }
+
+    $self->ct_file_set_contents($filename, $inittab);
+}
+
+sub remove_gateway_scripts {
+    my ($attr) = @_;
+    my $length = scalar(@$attr);
+
+    my $found_section = 0;
+    my $keep = 1;
+    @$attr = grep {
+       if ($_ eq '# --- BEGIN PVE ---') {
+           $found_section = 1;
+           $keep = 0;
+           0; # remove this line
+       } elsif ($_ eq '# --- END PVE ---') {
+           $found_section = 1;
+           $keep = 1;
+           0; # remove this line
+       } else {
+           $keep;
+       }
+    } @$attr;
+
+    return if $found_section;
+    # XXX: To deal with existing setups we perform two types of removal for
+    # now. Newly started containers have their routing sections marked with
+    # begin/end comments. For older containers we perform a strict matching on
+    # the routing rules we added. We can probably remove this part at some point
+    # when it is unlikely that old debian setups are still around.
+
+    for (my $i = 0; $i < $length-3; ++$i) {
+       next if $attr->[$i+0] !~ m@^\s*post-up\s+ip\s+route\s+add\s+(\S+)\s+dev\s+(\S+)$@;
+       my ($ip, $dev) = ($1, $2);
+       if ($attr->[$i+1] =~ m@^\s*post-up\s+ip\s+route\s+add\s+default\s+via\s+(\S+)\s+dev\s+(\S+)$@ &&
+           ($ip eq $1 && $dev eq $2) &&
+           $attr->[$i+2] =~ m@^\s*pre-down\s+ip\s+route\s+del\s+default\s+via\s+(\S+)\s+dev\s+(\S+)$@ &&
+           ($ip eq $1 && $dev eq $2) &&
+           $attr->[$i+3] =~ m@^\s*pre-down\s+ip\s+route\s+del\s+(\S+)\s+dev\s+(\S+)$@ &&
+           ($ip eq $1 && $dev eq $2))
+       {
+           splice @$attr, $i, 4;
+           $length -= 4;
+           --$i;
+       }
+    }
+}
+
+sub make_gateway_scripts {
+    my ($ifname, $gw) = @_;
+    return <<"SCRIPTS";
+# --- BEGIN PVE ---
+\tpost-up ip route add $gw dev $ifname
+\tpost-up ip route add default via $gw dev $ifname
+\tpre-down ip route del default via $gw dev $ifname
+\tpre-down ip route del $gw dev $ifname
+# --- END PVE ---
+SCRIPTS
 }
 
 sub setup_network {
@@ -106,9 +145,10 @@ sub setup_network {
     foreach my $k (keys %$conf) {
        next if $k !~ m/^net(\d+)$/;
        my $ind = $1;
-       my $d = PVE::LXC::parse_lxc_network($conf->{$k});
+       my $d = PVE::LXC::Config->parse_lxc_network($conf->{$k});
        if ($d->{name}) {
            my $net = {};
+           my $cidr;
            if (defined($d->{ip})) {
                if ($d->{ip} =~ /^(?:dhcp|manual)$/) {
                    $net->{address} = $d->{ip};
@@ -116,11 +156,17 @@ sub setup_network {
                    my $ipinfo = PVE::LXC::parse_ipv4_cidr($d->{ip});
                    $net->{address} = $ipinfo->{address};
                    $net->{netmask} = $ipinfo->{netmask};
+                   $cidr = $d->{ip};
                }
            }
            if (defined($d->{'gw'})) {
                $net->{gateway} = $d->{'gw'};
+               if (defined($cidr) && !PVE::Network::is_ip_in_cidr($d->{gw}, $cidr, 4)) {
+                   # gateway is not reachable, need an extra route
+                   $net->{needsroute} = 1;
+               }
            }
+           $cidr = undef;
            if (defined($d->{ip6})) {
                if ($d->{ip6} =~ /^(?:auto|dhcp|manual)$/) {
                    $net->{address6} = $d->{ip6};
@@ -129,10 +175,16 @@ sub setup_network {
                } else {
                    $net->{address6} = $1;
                    $net->{netmask6} = $2;
+                   $cidr = $d->{ip6};
                }
            }
            if (defined($d->{'gw6'})) {
                $net->{gateway6} = $d->{'gw6'};
+               if (defined($cidr) && !PVE::Network::is_ip_in_cidr($d->{gw6}, $cidr, 6) &&
+                   !PVE::Network::is_ip_in_cidr($d->{gw6}, 'fe80::/10', 6)) {
+                   # gateway is not reachable, need an extra route
+                   $net->{needsroute6} = 1;
+               }
            }
            $networks->{$d->{name}} = $net if keys %$net;
        }
@@ -145,61 +197,86 @@ sub setup_network {
 
     my $section;
 
+    my $done_auto = {};
     my $done_v4_hash = {};
     my $done_v6_hash = {};
-    
+
     my $print_section = sub {
-       my ($new) = @_;
-       
        return if !$section;
 
-       my $net = $networks->{$section->{ifname}};
+       my $ifname = $section->{ifname};
+       my $net = $networks->{$ifname};
 
-       if ($section->{type} eq 'ipv4') {
-           $done_v4_hash->{$section->{ifname}} = 1;
+       if (!$done_auto->{$ifname}) {
+           $interfaces .= "auto $ifname\n";
+           $done_auto->{$ifname} = 1;
+       }
 
-           $interfaces .= "auto $section->{ifname}\n" if $new;
+       if ($section->{type} eq 'ipv4') {
+           $done_v4_hash->{$ifname} = 1;
 
-           if ($net->{address} =~ /^(dhcp|manual)$/) {
-               $interfaces .= "iface $section->{ifname} inet $1\n";
-           } elsif ($net->{address}) {
-               $interfaces .= "iface $section->{ifname} inet static\n";
+           if (!defined($net->{address})) {
+               # no address => no iface line
+           } elsif ($net->{address} =~ /^(dhcp|manual)$/) {
+               $interfaces .= "iface $ifname inet $1\n\n";
+           } else {
+               $interfaces .= "iface $ifname inet static\n";
                $interfaces .= "\taddress $net->{address}\n" if defined($net->{address});
                $interfaces .= "\tnetmask $net->{netmask}\n" if defined($net->{netmask});
-               $interfaces .= "\tgateway $net->{gateway}\n" if defined($net->{gateway});
+
+               remove_gateway_scripts($section->{attr});
+               if (defined(my $gw = $net->{gateway})) {
+                   if ($net->{needsroute}) {
+                       $interfaces .= make_gateway_scripts($ifname, $gw);
+                   } else {
+                       $interfaces .= "\tgateway $gw\n";
+                   }
+               }
                foreach my $attr (@{$section->{attr}}) {
                    $interfaces .= "\t$attr\n";
                }
+               $interfaces .= "\n";
            }
-           
-           $interfaces .= "\n";
-           
        } elsif ($section->{type} eq 'ipv6') {
-           $done_v6_hash->{$section->{ifname}} = 1;
-           
-           if ($net->{address6} =~ /^(auto|dhcp|manual)$/) {
-               $interfaces .= "iface $section->{ifname} inet6 $1\n";
-           } elsif ($net->{address6}) {
-               $interfaces .= "iface $section->{ifname} inet6 static\n";
+           $done_v6_hash->{$ifname} = 1;
+
+           if (!defined($net->{address6})) {
+               # no address => no iface line
+           } elsif ($net->{address6} =~ /^(auto|dhcp|manual)$/) {
+               $interfaces .= "iface $ifname inet6 $1\n\n";
+           } else {
+               $interfaces .= "iface $ifname inet6 static\n";
                $interfaces .= "\taddress $net->{address6}\n" if defined($net->{address6});
                $interfaces .= "\tnetmask $net->{netmask6}\n" if defined($net->{netmask6});
-               $interfaces .= "\tgateway $net->{gateway6}\n" if defined($net->{gateway6});
+               remove_gateway_scripts($section->{attr});
+               if (defined(my $gw = $net->{gateway6})) {
+                   if ($net->{needsroute6}) {
+                       $interfaces .= make_gateway_scripts($ifname, $gw);
+                   } else {
+                       $interfaces .= "\tgateway $net->{gateway6}\n" if defined($net->{gateway6});
+                   }
+               }
                foreach my $attr (@{$section->{attr}}) {
                    $interfaces .= "\t$attr\n";
                }
+               $interfaces .= "\n";
            }
-           
-           $interfaces .= "\n";        
        } else {
            die "unknown section type '$section->{type}'";
        }
 
        $section = undef;
     };
-       
-    if (my $fh = $self->ct_open_file($filename, "r")) {
+
+    if (my $fh = $self->ct_open_file_read($filename)) {
        while (defined (my $line = <$fh>)) {
            chomp $line;
+           if ($line =~ m/^# --- (?:BEGIN|END) PVE ---/) {
+               # Include markers in the attribute section so
+               # remove_gateway_scripts() can find them.
+               push @{$section->{attr}}, $line if $section;
+               next;
+           }
            if ($line =~ m/^#/) {
                $interfaces .= "$line\n";
                next;
@@ -232,9 +309,17 @@ sub setup_network {
                $section = { type => 'ipv6', ifname => $ifname, attr => []};
                next;
            }
+           # Handle 'auto'
+           if ($line =~ m/^\s*auto\s*(.*)$/) {
+               foreach my $iface (split(/\s+/, $1)) {
+                   $done_auto->{$iface} = 1;
+               }
+               &$print_section();
+               $interfaces .= "$line\n";
+               next;
+           }
            # Handle other section delimiters:
            if ($line =~ m/^\s*(?:mapping\s
-                                |auto\s
                                 |allow-
                                 |source\s
                                 |source-directory\s
@@ -249,33 +334,40 @@ sub setup_network {
                    $aname eq 'gateway' || $aname eq 'broadcast') {
                    # skip
                } else {
-                   push @{$section->{attr}}, $adata; 
+                   push @{$section->{attr}}, $adata;
                }
                next;
            }
-           
-           $interfaces .= "$line\n";       
+
+           $interfaces .= "$line\n";
        }
        &$print_section();
-       
     }
 
-    my $need_separator = 1;
+    my $need_separator = length($interfaces) && ($interfaces !~ /\n\n$/);
     foreach my $ifname (sort keys %$networks) {
        my $net = $networks->{$ifname};
-       
-       if (!$done_v4_hash->{$ifname}) {
-           if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };     
+
+       if (!$done_v4_hash->{$ifname} && defined($net->{address})) {
+           if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };
            $section = { type => 'ipv4', ifname => $ifname, attr => []};
-           &$print_section(1);
+           &$print_section();
        }
        if (!$done_v6_hash->{$ifname} && defined($net->{address6})) {
-           if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };     
+           if ($need_separator) { $interfaces .= "\n"; $need_separator = 0; };
            $section = { type => 'ipv6', ifname => $ifname, attr => []};
-           &$print_section(1);
+           &$print_section();
        }
     }
-    
+
+    # older templates (< Debian 8) do not configure the loopback interface
+    # if not explicitly told to do so
+    if (!$done_auto->{lo}) {
+       $interfaces = "auto lo\niface lo inet loopback\n" .
+                     "iface lo inet6 loopback\n\n" .
+                     $interfaces;
+    }
+
     $self->ct_file_set_contents($filename, $interfaces);
 }