]>
git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Setup/Base.pm
1 package PVE
::LXC
::Setup
::Base
;
19 my ($class, $conf, $rootdir) = @_;
21 return bless { conf
=> $conf, rootdir
=> $rootdir }, $class;
25 my ($self, $conf) = @_;
27 my $nameserver = $conf->{nameserver
};
28 my $searchdomains = $conf->{searchdomain
};
30 if (!($nameserver && $searchdomains)) {
32 if ($conf->{'testmode'}) {
34 $nameserver = "8.8.8.8 8.8.8.9";
35 $searchdomains = "proxmox.com";
39 my $host_resolv_conf = $self->{host_resolv_conf
};
41 $searchdomains = $host_resolv_conf->{search
};
44 foreach my $k ("dns1", "dns2", "dns3") {
45 if (my $ns = $host_resolv_conf->{$k}) {
49 $nameserver = join(' ', @list);
53 return ($searchdomains, $nameserver);
56 sub update_etc_hosts
{
57 my ($etc_hosts_data, $hostip, $oldname, $newname, $searchdomains) = @_;
64 foreach my $domain (PVE
::Tools
::split_list
($searchdomains)) {
65 $extra_names .= ' ' if $extra_names;
66 $extra_names .= "$newname.$domain";
69 foreach my $line (split(/\n/, $etc_hosts_data)) {
70 if ($line =~ m/^#/ || $line =~ m/^\s*$/) {
75 my ($ip, @names) = split(/\s+/, $line);
76 if (($ip eq '127.0.0.1') || ($ip eq '::1')) {
82 foreach my $name (@names) {
83 if ($name eq $oldname || $name eq $newname) {
86 # fixme: record extra names?
89 $found = 1 if defined($hostip) && ($ip eq $hostip);
93 if (defined($hostip)) {
94 push @lines, "$hostip $extra_names $newname";
96 push @lines, "127.0.1.1 $newname";
107 if (defined($hostip)) {
108 push @lines, "$hostip $extra_names $newname";
110 push @lines, "127.0.1.1 $newname";
114 my $found_localhost = 0;
115 foreach my $line (@lines) {
116 if ($line =~ m/^127.0.0.1\s/) {
117 $found_localhost = 1;
122 if (!$found_localhost) {
123 unshift @lines, "127.0.0.1 localhost.localnet localhost";
126 $etc_hosts_data = join("\n", @lines) . "\n";
128 return $etc_hosts_data;
132 my ($self, $conf) = @_;
134 # do nothing by default
138 my ($self, $conf) = @_;
140 my ($searchdomains, $nameserver) = $self->lookup_dns_conf($conf);
144 $data .= "search " . join(' ', PVE
::Tools
::split_list
($searchdomains)) . "\n"
147 foreach my $ns ( PVE
::Tools
::split_list
($nameserver)) {
148 $data .= "nameserver $ns\n";
151 $self->ct_file_set_contents("/etc/resolv.conf", $data);
155 my ($self, $conf) = @_;
157 my $hostname = $conf->{hostname
} || 'localhost';
159 $hostname =~ s/\..*$//;
161 my $hostname_fn = "/etc/hostname";
163 my $oldname = $self->ct_file_read_firstline($hostname_fn) || 'localhost';
165 my $hosts_fn = "/etc/hosts";
166 my $etc_hosts_data = '';
168 if ($self->ct_file_exists($hosts_fn)) {
169 $etc_hosts_data = $self->ct_file_get_contents($hosts_fn);
172 my ($ipv4, $ipv6) = PVE
::LXC
::get_primary_ips
($conf);
173 my $hostip = $ipv4 || $ipv6;
175 my ($searchdomains) = $self->lookup_dns_conf($conf);
177 $etc_hosts_data = update_etc_hosts
($etc_hosts_data, $hostip, $oldname,
178 $hostname, $searchdomains);
180 $self->ct_file_set_contents($hostname_fn, "$hostname\n");
181 $self->ct_file_set_contents($hosts_fn, $etc_hosts_data);
185 my ($self, $conf) = @_;
187 die "please implement this inside subclass"
191 my ($self, $conf) = @_;
193 die "please implement this inside subclass"
196 sub setup_systemd_console
{
197 my ($self, $conf) = @_;
199 my $systemd_dir_rel = -x
"/lib/systemd/systemd" ?
200 "/lib/systemd/system" : "/usr/lib/systemd/system";
202 my $systemd_getty_service_rel = "$systemd_dir_rel/getty\@.service";
204 return if !$self->ct_file_exists($systemd_getty_service_rel);
206 my $raw = $self->ct_file_get_contents($systemd_getty_service_rel);
208 my $systemd_container_getty_service_rel = "$systemd_dir_rel/container-getty\@.service";
210 # systemd on CenoOS 7.1 is too old (version 205), so there is no
211 # container-getty service
212 if (!$self->ct_file_exists($systemd_container_getty_service_rel)) {
213 if ($raw =~ s!^ConditionPathExists=/dev/tty0$!ConditionPathExists=/dev/tty!m) {
214 $self->ct_file_set_contents($systemd_getty_service_rel, $raw);
217 # undo above change (in case someone updated systemd)
218 if ($raw =~ s!^ConditionPathExists=/dev/tty$!ConditionPathExists=/dev/tty0!m) {
219 $self->ct_file_set_contents($systemd_getty_service_rel, $raw);
223 my $ttycount = PVE
::LXC
::get_tty_count
($conf);
225 for (my $i = 1; $i < 7; $i++) {
226 my $tty_service_lnk = "/etc/systemd/system/getty.target.wants/getty\@tty$i.service";
227 if ($i > $ttycount) {
228 $self->ct_unlink($tty_service_lnk);
230 if (!$self->ct_is_symlink($tty_service_lnk)) {
231 $self->ct_unlink($tty_service_lnk);
232 $self->ct_symlink($systemd_getty_service_rel, $tty_service_lnk);
238 sub setup_systemd_networkd
{
239 my ($self, $conf) = @_;
241 foreach my $k (keys %$conf) {
242 next if $k !~ m/^net(\d+)$/;
243 my $d = PVE
::LXC
::parse_lxc_network
($conf->{$k});
246 my $filename = "/etc/systemd/network/$d->{name}.network";
253 Description = Interface $d->{name} autoconfigured by PVE
257 my ($has_ipv4, $has_ipv6);
260 my @DHCPMODES = ('none', 'v4', 'v6', 'both');
261 my ($NONE, $DHCP4, $DHCP6, $BOTH) = (0, 1, 2, 3);
264 if (defined(my $ip = $d->{ip
})) {
267 } elsif ($ip ne 'manual') {
269 $data .= "Address = $ip\n";
272 if (defined(my $gw = $d->{gw
})) {
273 $data .= "Gateway = $gw\n";
274 if ($has_ipv4 && !PVE
::Network
::is_ip_in_cidr
($gw, $d->{ip
}, 4)) {
275 $routes .= "\n[Route]\nDestination = $gw/32\nScope = link\n";
279 if (defined(my $ip = $d->{ip6
})) {
282 } elsif ($ip ne 'manual') {
284 $data .= "Address = $ip\n";
287 if (defined(my $gw = $d->{gw6
})) {
288 $data .= "Gateway = $gw\n";
289 if ($has_ipv6 && !PVE
::Network
::is_ip_in_cidr
($gw, $d->{ip6
}, 6)) {
290 $routes .= "\n[Route]\nDestination = $gw/128\nScope = link\n";
294 $data .= "DHCP = $DHCPMODES[$dhcp]\n";
295 $data .= $routes if $routes;
297 $self->ct_file_set_contents($filename, $data);
301 sub setup_securetty
{
302 my ($self, $conf, @add) = @_;
304 my $filename = "/etc/securetty";
305 my $data = $self->ct_file_get_contents($filename);
306 chomp $data; $data .= "\n";
307 foreach my $dev (@add) {
308 if ($data !~ m!^\Q$dev\E\s*$!m) {
312 $self->ct_file_set_contents($filename, $data);
315 my $replacepw = sub {
316 my ($self, $file, $user, $epw, $shadow) = @_;
318 my $tmpfile = "$file.$$";
321 my $src = $self->ct_open_file_read($file) ||
322 die "unable to open file '$file' - $!";
324 my $st = $self->ct_stat($src) ||
325 die "unable to stat file - $!";
327 my $dst = $self->ct_open_file_write($tmpfile) ||
328 die "unable to open file '$tmpfile' - $!";
330 # copy owner and permissions
331 chmod $st->mode, $dst;
332 chown $st->uid, $st->gid, $dst;
334 my $last_change = int(time()/(60*60*24));
336 if ($epw =~ m/^\$TEST\$/) { # for regression tests
337 $last_change = 12345;
340 while (defined (my $line = <$src>)) {
342 $line =~ s/^${user}:[^:]*:[^:]*:/${user}:${epw}:${last_change}:/;
344 $line =~ s/^${user}:[^:]*:/${user}:${epw}:/;
349 $src->close() || die "close '$file' failed - $!\n";
350 $dst->close() || die "close '$tmpfile' failed - $!\n";
353 $self->ct_unlink($tmpfile);
355 $self->ct_rename($tmpfile, $file);
356 $self->ct_unlink($tmpfile); # in case rename fails
360 sub set_user_password
{
361 my ($self, $conf, $user, $opt_password) = @_;
363 my $pwfile = "/etc/passwd";
365 return if !$self->ct_file_exists($pwfile);
367 my $shadow = "/etc/shadow";
369 if (defined($opt_password)) {
370 if ($opt_password !~ m/^\$/) {
371 my $time = substr (Digest
::SHA
::sha1_base64
(time), 0, 8);
372 $opt_password = crypt(encode
("utf8", $opt_password), "\$1\$$time\$");
378 if ($self->ct_file_exists($shadow)) {
379 &$replacepw ($self, $shadow, $user, $opt_password, 1);
380 &$replacepw ($self, $pwfile, $user, 'x');
382 &$replacepw ($self, $pwfile, $user, $opt_password);
386 my $randomize_crontab = sub {
387 my ($self, $conf) = @_;
390 # Note: dir_glob_foreach() untaints filenames!
391 PVE
::Tools
::dir_glob_foreach
("/etc/cron.d", qr/[A-Z\-\_a-z0-9]+/, sub {
393 push @files, "/etc/cron.d/$name";
396 my $crontab_fn = "/etc/crontab";
397 unshift @files, $crontab_fn if $self->ct_file_exists($crontab_fn);
399 foreach my $filename (@files) {
400 my $data = $self->ct_file_get_contents($filename);
402 foreach my $line (split(/\n/, $data)) {
403 # we only randomize minutes for root crontab entries
404 if ($line =~ m/^\d+(\s+\S+\s+\S+\s+\S+\s+\S+\s+root\s+\S.*)$/) {
406 my $min = int(rand()*59);
407 $new .= "$min$rest\n";
412 $self->ct_file_set_contents($filename, $new);
417 my ($self, $conf) = @_;
419 $self->setup_init($conf);
420 $self->setup_network($conf);
421 $self->set_hostname($conf);
422 $self->set_dns($conf);
427 sub post_create_hook
{
428 my ($self, $conf, $root_password) = @_;
430 $self->template_fixup($conf);
432 &$randomize_crontab($self, $conf);
434 $self->set_user_password($conf, 'root', $root_password);
435 $self->setup_init($conf);
436 $self->setup_network($conf);
437 $self->set_hostname($conf);
438 $self->set_dns($conf);
443 # File access wrappers for container setup code.
444 # For user-namespace support these might need to take uid and gid maps into account.
447 my ($self, $file, $mask) = @_;
448 # mkdir goes by parameter count - an `undef' mode acts like a mode of 0000
449 return CORE
::mkdir($file, $mask) if defined ($mask);
450 return CORE
::mkdir($file);
454 my ($self, @files) = @_;
455 foreach my $file (@files) {
461 my ($self, $old, $new) = @_;
462 CORE
::rename($old, $new);
465 sub ct_open_file_read
{
468 return IO
::File-
>new($file, O_RDONLY
, @_);
471 sub ct_open_file_write
{
474 return IO
::File-
>new($file, O_WRONLY
| O_CREAT
, @_);
479 File
::Path
::make_path
(@_);
483 my ($self, $old, $new) = @_;
484 return CORE
::symlink($old, $new);
488 my ($self, $file) = @_;
492 sub ct_is_directory
{
493 my ($self, $file) = @_;
498 my ($self, $file) = @_;
503 my ($self, $file) = @_;
504 return File
::stat::stat($file);
507 sub ct_file_read_firstline
{
508 my ($self, $file) = @_;
509 return PVE
::Tools
::file_read_firstline
($file);
512 sub ct_file_get_contents
{
513 my ($self, $file) = @_;
514 return PVE
::Tools
::file_get_contents
($file);
517 sub ct_file_set_contents
{
518 my ($self, $file, $data) = @_;
519 return PVE
::Tools
::file_set_contents
($file, $data);