]>
git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Setup/Base.pm
1 package PVE
::LXC
::Setup
::Base
;
16 my ($class, $conf, $rootdir) = @_;
18 return bless { conf
=> $conf, rootdir
=> $rootdir }, $class;
24 my $nameserver = $conf->{nameserver
};
25 my $searchdomains = $conf->{searchdomain
};
27 if (!($nameserver && $searchdomains)) {
29 if ($conf->{'testmode'}) {
31 $nameserver = "8.8.8.8 8.8.8.9";
32 $searchdomains = "promxox.com";
36 my $host_resolv_conf = PVE
::INotify
::read_file
('resolvconf');
38 $searchdomains = $host_resolv_conf->{search
};
41 foreach my $k ("dns1", "dns2", "dns3") {
42 if (my $ns = $host_resolv_conf->{$k}) {
46 $nameserver = join(' ', @list);
50 return ($searchdomains, $nameserver);
53 sub update_etc_hosts
{
54 my ($etc_hosts_data, $hostip, $oldname, $newname, $searchdomains) = @_;
61 foreach my $domain (PVE
::Tools
::split_list
($searchdomains)) {
62 $extra_names .= ' ' if $extra_names;
63 $extra_names .= "$newname.$domain";
66 foreach my $line (split(/\n/, $etc_hosts_data)) {
67 if ($line =~ m/^#/ || $line =~ m/^\s*$/) {
72 my ($ip, @names) = split(/\s+/, $line);
73 if (($ip eq '127.0.0.1') || ($ip eq '::1')) {
79 foreach my $name (@names) {
80 if ($name eq $oldname || $name eq $newname) {
83 # fixme: record extra names?
86 $found = 1 if defined($hostip) && ($ip eq $hostip);
90 if (defined($hostip)) {
91 push @lines, "$hostip $extra_names $newname";
93 push @lines, "127.0.1.1 $newname";
104 if (defined($hostip)) {
105 push @lines, "$hostip $extra_names $newname";
107 push @lines, "127.0.1.1 $newname";
111 my $found_localhost = 0;
112 foreach my $line (@lines) {
113 if ($line =~ m/^127.0.0.1\s/) {
114 $found_localhost = 1;
119 if (!$found_localhost) {
120 unshift @lines, "127.0.0.1 localhost.localnet localhost";
123 $etc_hosts_data = join("\n", @lines) . "\n";
125 return $etc_hosts_data;
129 my ($self, $conf) = @_;
131 # do nothing by default
135 my ($self, $conf) = @_;
137 my ($searchdomains, $nameserver) = lookup_dns_conf
($conf);
139 my $rootdir = $self->{rootdir
};
141 my $filename = "$rootdir/etc/resolv.conf";
145 $data .= "search " . join(' ', PVE
::Tools
::split_list
($searchdomains)) . "\n"
148 foreach my $ns ( PVE
::Tools
::split_list
($nameserver)) {
149 $data .= "nameserver $ns\n";
152 PVE
::Tools
::file_set_contents
($filename, $data);
156 my ($self, $conf) = @_;
158 my $hostname = $conf->{hostname
} || 'localhost';
160 $hostname =~ s/\..*$//;
162 my $rootdir = $self->{rootdir
};
164 my $hostname_fn = "$rootdir/etc/hostname";
166 my $oldname = PVE
::Tools
::file_read_firstline
($hostname_fn) || 'localhost';
168 my $hosts_fn = "$rootdir/etc/hosts";
169 my $etc_hosts_data = '';
172 $etc_hosts_data = PVE
::Tools
::file_get_contents
($hosts_fn);
175 my ($ipv4, $ipv6) = PVE
::LXC
::get_primary_ips
($conf);
176 my $hostip = $ipv4 || $ipv6;
178 my ($searchdomains) = lookup_dns_conf
($conf);
180 $etc_hosts_data = update_etc_hosts
($etc_hosts_data, $hostip, $oldname,
181 $hostname, $searchdomains);
183 PVE
::Tools
::file_set_contents
($hostname_fn, "$hostname\n");
184 PVE
::Tools
::file_set_contents
($hosts_fn, $etc_hosts_data);
188 my ($self, $conf) = @_;
190 die "please implement this inside subclass"
194 my ($self, $conf) = @_;
196 die "please implement this inside subclass"
199 sub setup_systemd_console
{
200 my ($self, $conf) = @_;
202 my $rootdir = $self->{rootdir
};
204 my $systemd_dir_rel = -x
"$rootdir/lib/systemd/systemd" ?
205 "/lib/systemd/system" : "/usr/lib/systemd/system";
207 my $systemd_dir = "$rootdir/$systemd_dir_rel";
209 my $etc_systemd_dir = "$rootdir/etc/systemd/system";
211 my $systemd_getty_service_rel = "$systemd_dir_rel/getty\@.service";
213 my $systemd_getty_service = "$rootdir/$systemd_getty_service_rel";
215 return if ! -f
$systemd_getty_service;
217 my $raw = PVE
::Tools
::file_get_contents
($systemd_getty_service);
219 my $systemd_container_getty_service_rel = "$systemd_dir_rel/container-getty\@.service";
220 my $systemd_container_getty_service = "$rootdir/$systemd_container_getty_service_rel";
222 # systemd on CenoOS 7.1 is too old (version 205), so there is no
223 # container-getty service
224 if (! -f
$systemd_container_getty_service) {
225 if ($raw =~ s!^ConditionPathExists=/dev/tty0$!ConditionPathExists=/dev/tty!m) {
226 PVE
::Tools
::file_set_contents
($systemd_getty_service, $raw);
229 # undo above change (in case someone updated systemd)
230 if ($raw =~ s!^ConditionPathExists=/dev/tty$!ConditionPathExists=/dev/tty0!m) {
231 PVE
::Tools
::file_set_contents
($systemd_getty_service, $raw);
235 my $ttycount = PVE
::LXC
::get_tty_count
($conf);
237 for (my $i = 1; $i < 7; $i++) {
238 my $tty_service_lnk = "$etc_systemd_dir/getty.target.wants/getty\@tty$i.service";
239 if ($i > $ttycount) {
240 unlink $tty_service_lnk;
242 if (! -l
$tty_service_lnk) {
243 unlink $tty_service_lnk;
244 symlink($systemd_getty_service_rel, $tty_service_lnk);
250 sub setup_systemd_networkd
{
251 my ($self, $conf) = @_;
253 my $rootdir = $self->{rootdir
};
255 foreach my $k (keys %$conf) {
256 next if $k !~ m/^net(\d+)$/;
257 my $d = PVE
::LXC
::parse_lxc_network
($conf->{$k});
260 my $filename = "$rootdir/etc/systemd/network/$d->{name}.network";
267 Description = Interface $d->{name} autoconfigured by PVE
270 my @DHCPMODES = ('none', 'v4', 'v6', 'both');
271 my ($NONE, $DHCP4, $DHCP6, $BOTH) = (0, 1, 2, 3);
274 if (defined(my $ip = $d->{ip
})) {
277 } elsif ($ip ne 'manual') {
278 $data .= "Address = $ip\n";
281 if (defined(my $gw = $d->{gw
})) {
282 $data .= "Gateway = $gw\n";
285 if (defined(my $ip = $d->{ip6
})) {
288 } elsif ($ip ne 'manual') {
289 $data .= "Address = $ip\n";
292 if (defined(my $gw = $d->{gw6
})) {
293 $data .= "Gateway = $gw\n";
296 $data .= "DHCP = $DHCPMODES[$dhcp]\n";
298 PVE
::Tools
::file_set_contents
($filename, $data);
302 sub setup_securetty
{
303 my ($self, $conf, @add) = @_;
305 my $rootdir = $self->{rootdir
};
306 my $filename = "$rootdir/etc/securetty";
307 my $data = PVE
::Tools
::file_get_contents
($filename);
308 chomp $data; $data .= "\n";
309 foreach my $dev (@add) {
310 if ($data !~ m!^\Q$dev\E\s*$!m) {
314 PVE
::Tools
::file_set_contents
($filename, $data);
317 my $replacepw = sub {
318 my ($file, $user, $epw, $shadow) = @_;
320 my $tmpfile = "$file.$$";
323 my $src = IO
::File-
>new("<$file") ||
324 die "unable to open file '$file' - $!";
326 my $st = File
::stat::stat($src) ||
327 die "unable to stat file - $!";
329 my $dst = IO
::File-
>new(">$tmpfile") ||
330 die "unable to open file '$tmpfile' - $!";
332 # copy owner and permissions
333 chmod $st->mode, $dst;
334 chown $st->uid, $st->gid, $dst;
336 my $last_change = int(time()/(60*60*24));
338 if ($epw =~ m/^\$TEST\$/) { # for regression tests
339 $last_change = 12345;
342 while (defined (my $line = <$src>)) {
344 $line =~ s/^${user}:[^:]*:[^:]*:/${user}:${epw}:${last_change}:/;
346 $line =~ s/^${user}:[^:]*:/${user}:${epw}:/;
351 $src->close() || die "close '$file' failed - $!\n";
352 $dst->close() || die "close '$tmpfile' failed - $!\n";
357 rename $tmpfile, $file;
358 unlink $tmpfile; # in case rename fails
362 sub set_user_password
{
363 my ($self, $conf, $user, $opt_password) = @_;
365 my $rootdir = $self->{rootdir
};
367 my $pwfile = "$rootdir/etc/passwd";
369 return if ! -f
$pwfile;
371 my $shadow = "$rootdir/etc/shadow";
373 if (defined($opt_password)) {
374 if ($opt_password !~ m/^\$/) {
375 my $time = substr (Digest
::SHA
::sha1_base64
(time), 0, 8);
376 $opt_password = crypt(encode
("utf8", $opt_password), "\$1\$$time\$");
383 &$replacepw ($shadow, $user, $opt_password, 1);
384 &$replacepw ($pwfile, $user, 'x');
386 &$replacepw ($pwfile, $user, $opt_password);
390 my $randomize_crontab = sub {
391 my ($self, $conf) = @_;
393 my $rootdir = $self->{rootdir
};
396 # Note: dir_glob_foreach() untaints filenames!
397 my $cron_dir = "$rootdir/etc/cron.d";
398 PVE
::Tools
::dir_glob_foreach
($cron_dir, qr/[A-Z\-\_a-z0-9]+/, sub {
400 push @files, "$cron_dir/$name";
403 my $crontab_fn = "$rootdir/etc/crontab";
404 unshift @files, $crontab_fn if -f
$crontab_fn;
406 foreach my $filename (@files) {
407 my $data = PVE
::Tools
::file_get_contents
($filename);
409 foreach my $line (split(/\n/, $data)) {
410 # we only randomize minutes for root crontab entries
411 if ($line =~ m/^\d+(\s+\S+\s+\S+\s+\S+\s+\S+\s+root\s+\S.*)$/) {
413 my $min = int(rand()*59);
414 $new .= "$min$rest\n";
419 PVE
::Tools
::file_set_contents
($filename, $new);
423 sub rewrite_ssh_host_keys
{
424 my ($self, $conf) = @_;
426 my $rootdir = $self->{rootdir
};
428 my $etc_ssh_dir = "$rootdir/etc/ssh";
430 return if ! -d
$etc_ssh_dir;
433 rsa1
=> 'ssh_host_key',
434 rsa
=> 'ssh_host_rsa_key',
435 dsa
=> 'ssh_host_dsa_key',
436 ecdsa
=> 'ssh_host_ecdsa_key',
437 ed25519
=> 'ssh_host_ed25519_key',
440 my $hostname = $conf->{hostname
} || 'localhost';
441 $hostname =~ s/\..*$//;
443 foreach my $keytype (keys %$keynames) {
444 my $basename = $keynames->{$keytype};
445 unlink "${etc_ssh_dir}/$basename";
446 unlink "${etc_ssh_dir}/$basename.pub";
447 print "Creating SSH host key '$basename' - this may take some time ...\n";
448 my $cmd = ['ssh-keygen', '-q', '-f', "${etc_ssh_dir}/$basename", '-t', $keytype,
449 '-N', '', '-C', "root\@$hostname"];
450 PVE
::Tools
::run_command
($cmd);
455 my ($self, $conf) = @_;
457 $self->setup_init($conf);
458 $self->setup_network($conf);
459 $self->set_hostname($conf);
460 $self->set_dns($conf);
465 sub post_create_hook
{
466 my ($self, $conf, $root_password) = @_;
468 $self->template_fixup($conf);
470 &$randomize_crontab($self, $conf);
472 $self->set_user_password($conf, 'root', $root_password);
473 $self->setup_init($conf);
474 $self->setup_network($conf);
475 $self->set_hostname($conf);
476 $self->set_dns($conf);
477 $self->rewrite_ssh_host_keys($conf);
483 my ($self, $file, $mask) = @_;
484 my $root = $self->{rootdir
};
485 $file //= $_; # emulate mkdir parameters
486 return CORE
::mkdir("$root/$file", $mask) if defined ($mask);
487 return CORE
::mkdir("$root/$file");
492 my $root = $self->{rootdir
};
493 return CORE
::unlink("$root/$_") if !@_; # emulate unlink parameters
494 return CORE
::unlink(map { "$root/$_" } @_);
499 my $file = $self->{rootdir
} . '/' . shift;
500 return IO
::File-
>new($file, @_);
505 my $root = $self->{rootdir
};
507 $opt = "$root/$opt" if ref($opt) ne 'HASH';
508 return File
::Path
::make_path
(map { "$root/$_" } @_, $opt);
513 my $root = $self->{rootdir
};
516 return File
::Path
::mkpath
(map { "$root/$_" } @$first, @_) if ref($first) eq 'ARRAY';
519 return File
::Path
::mkpath
(map { "$root/$_" } @_, $last) if ref($last) eq 'HASH';
520 return File
::Path
::mkpath
(map { "$root/$_" } (@_, $last||()));
524 my ($self, $old, $new) = @_;
525 my $root = $self->{rootdir
};
526 return CORE
::symlink($old, "$root/$new");
530 my ($self, $file) = @_;
531 my $root = $self->{rootdir
};
532 return -f
"$root/$file";
535 sub ct_file_read_firstline
{
536 my ($self, $file) = @_;
537 my $root = $self->{rootdir
};
538 return PVE
::Tools
::file_read_firstline
("$root/$file");
541 sub ct_file_get_contents
{
542 my ($self, $file) = @_;
543 my $root = $self->{rootdir
};
544 return PVE
::Tools
::file_get_contents
("$root/$file");
547 sub ct_file_set_contents
{
548 my ($self, $file, $data) = @_;
549 my $root = $self->{rootdir
};
550 return PVE
::Tools
::file_set_contents
("$root/$file", $data);