]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXCSetup/Base.pm
LXCSetup::Redhat: ipv6 config
[pve-container.git] / src / PVE / LXCSetup / Base.pm
CommitLineData
1c7f4f65
DM
1package PVE::LXCSetup::Base;
2
3use strict;
4use warnings;
5
168d6b07
DM
6use File::stat;
7use Digest::SHA;
8use IO::File;
9use Encode;
10
b9cd9975 11use PVE::INotify;
55fa4e09
DM
12use PVE::Tools;
13
633a7bd8 14sub new {
5b4657d0 15 my ($class, $conf, $rootdir) = @_;
633a7bd8 16
5b4657d0 17 return bless { conf => $conf, rootdir => $rootdir }, $class;
633a7bd8 18}
b9cd9975 19
c0eae401 20sub lookup_dns_conf {
b9cd9975
DM
21 my ($conf) = @_;
22
23 my $nameserver = $conf->{'pve.nameserver'};
24 my $searchdomains = $conf->{'pve.searchdomain'};
25
26 if (!($nameserver && $searchdomains)) {
27
28 if ($conf->{'pve.test_mode'}) {
29
30 $nameserver = "8.8.8.8 8.8.8.9";
31 $searchdomains = "promxox.com";
32
33 } else {
34
35 my $host_resolv_conf = PVE::INotify::read_file('resolvconf');
36
37 $searchdomains = $host_resolv_conf->{search};
38
39 my @list = ();
40 foreach my $k ("dns1", "dns2", "dns3") {
41 if (my $ns = $host_resolv_conf->{$k}) {
42 push @list, $ns;
43 }
44 }
45 $nameserver = join(' ', @list);
46 }
47 }
48
49 return ($searchdomains, $nameserver);
c0eae401 50}
b9cd9975 51
c0eae401 52sub update_etc_hosts {
e4929e97 53 my ($etc_hosts_data, $hostip, $oldname, $newname, $searchdomains) = @_;
1c7f4f65 54
1c7f4f65
DM
55 my $done = 0;
56
57 my @lines;
e4929e97
DM
58
59 my $extra_names = '';
60 foreach my $domain (PVE::Tools::split_list($searchdomains)) {
61 $extra_names .= ' ' if $extra_names;
62 $extra_names .= "$newname.$domain";
63 }
1c7f4f65
DM
64
65 foreach my $line (split(/\n/, $etc_hosts_data)) {
66 if ($line =~ m/^#/ || $line =~ m/^\s*$/) {
67 push @lines, $line;
68 next;
69 }
70
71 my ($ip, @names) = split(/\s+/, $line);
72 if (($ip eq '127.0.0.1') || ($ip eq '::1')) {
73 push @lines, $line;
74 next;
75 }
c325b32f 76
1c7f4f65
DM
77 my $found = 0;
78 foreach my $name (@names) {
79 if ($name eq $oldname || $name eq $newname) {
80 $found = 1;
81 } else {
82 # fixme: record extra names?
83 }
84 }
85 $found = 1 if defined($hostip) && ($ip eq $hostip);
86
87 if ($found) {
88 if (!$done) {
89 if (defined($hostip)) {
c325b32f 90 push @lines, "$hostip $extra_names $newname";
1c7f4f65
DM
91 } else {
92 push @lines, "127.0.1.1 $newname";
93 }
94 $done = 1;
95 }
96 next;
97 } else {
98 push @lines, $line;
99 }
100 }
101
102 if (!$done) {
103 if (defined($hostip)) {
e4929e97 104 push @lines, "$hostip $extra_names $newname";
1c7f4f65
DM
105 } else {
106 push @lines, "127.0.1.1 $newname";
107 }
108 }
109
1e180f97
DM
110 my $found_localhost = 0;
111 foreach my $line (@lines) {
112 if ($line =~ m/^127.0.0.1\s/) {
113 $found_localhost = 1;
114 last;
115 }
116 }
117
118 if (!$found_localhost) {
119 unshift @lines, "127.0.0.1 localhost.localnet localhost";
120 }
121
1c7f4f65
DM
122 $etc_hosts_data = join("\n", @lines) . "\n";
123
124 return $etc_hosts_data;
c0eae401 125}
1c7f4f65 126
142444d5
DM
127sub template_fixup {
128 my ($self, $conf) = @_;
129
130 # do nothing by default
131}
132
c325b32f 133sub set_dns {
633a7bd8 134 my ($self, $conf) = @_;
c325b32f 135
c0eae401 136 my ($searchdomains, $nameserver) = lookup_dns_conf($conf);
c325b32f 137
5b4657d0 138 my $rootdir = $self->{rootdir};
c325b32f 139
5b4657d0 140 my $filename = "$rootdir/etc/resolv.conf";
c325b32f
DM
141
142 my $data = '';
143
144 $data .= "search " . join(' ', PVE::Tools::split_list($searchdomains)) . "\n"
145 if $searchdomains;
146
147 foreach my $ns ( PVE::Tools::split_list($nameserver)) {
148 $data .= "nameserver $ns\n";
149 }
150
151 PVE::Tools::file_set_contents($filename, $data);
152}
153
1c7f4f65 154sub set_hostname {
633a7bd8 155 my ($self, $conf) = @_;
1c7f4f65 156
e4929e97 157 my $hostname = $conf->{'lxc.utsname'} || 'localhost';
1c7f4f65
DM
158
159 $hostname =~ s/\..*$//;
160
5b4657d0 161 my $rootdir = $self->{rootdir};
1c7f4f65 162
5b4657d0 163 my $hostname_fn = "$rootdir/etc/hostname";
1c7f4f65 164
e4929e97 165 my $oldname = PVE::Tools::file_read_firstline($hostname_fn) || 'localhost';
1c7f4f65 166
5b4657d0 167 my $hosts_fn = "$rootdir/etc/hosts";
1c7f4f65
DM
168 my $etc_hosts_data = '';
169
170 if (-f $hosts_fn) {
171 $etc_hosts_data = PVE::Tools::file_get_contents($hosts_fn);
172 }
173
c325b32f
DM
174 my ($ipv4, $ipv6) = PVE::LXC::get_primary_ips($conf);
175 my $hostip = $ipv4 || $ipv6;
b9cd9975 176
c0eae401 177 my ($searchdomains) = lookup_dns_conf($conf);
b9cd9975 178
c0eae401
DM
179 $etc_hosts_data = update_etc_hosts($etc_hosts_data, $hostip, $oldname,
180 $hostname, $searchdomains);
b9cd9975 181
1c7f4f65
DM
182 PVE::Tools::file_set_contents($hostname_fn, "$hostname\n");
183 PVE::Tools::file_set_contents($hosts_fn, $etc_hosts_data);
184}
185
55fa4e09 186sub setup_network {
633a7bd8 187 my ($self, $conf) = @_;
55fa4e09
DM
188
189 die "please implement this inside subclass"
190}
191
d66768a2 192sub setup_init {
633a7bd8 193 my ($self, $conf) = @_;
1c7f4f65 194
d66768a2
DM
195 die "please implement this inside subclass"
196}
197
168d6b07
DM
198my $replacepw = sub {
199 my ($file, $user, $epw) = @_;
200
201 my $tmpfile = "$file.$$";
202
203 eval {
204 my $src = IO::File->new("<$file") ||
205 die "unable to open file '$file' - $!";
206
207 my $st = File::stat::stat($src) ||
208 die "unable to stat file - $!";
209
210 my $dst = IO::File->new(">$tmpfile") ||
211 die "unable to open file '$tmpfile' - $!";
212
213 # copy owner and permissions
214 chmod $st->mode, $dst;
215 chown $st->uid, $st->gid, $dst;
216
217 while (defined (my $line = <$src>)) {
218 $line =~ s/^${user}:[^:]*:/${user}:${epw}:/;
219 print $dst $line;
220 }
221
222 $src->close() || die "close '$file' failed - $!\n";
223 $dst->close() || die "close '$tmpfile' failed - $!\n";
224 };
225 if (my $err = $@) {
226 unlink $tmpfile;
227 } else {
228 rename $tmpfile, $file;
229 unlink $tmpfile; # in case rename fails
230 }
231};
232
233sub set_user_password {
633a7bd8 234 my ($self, $conf, $user, $opt_password) = @_;
168d6b07 235
5b4657d0 236 my $rootdir = $self->{rootdir};
168d6b07 237
5b4657d0 238 my $pwfile = "$rootdir/etc/passwd";
168d6b07
DM
239
240 return if ! -f $pwfile;
241
5b4657d0 242 my $shadow = "$rootdir/etc/shadow";
168d6b07
DM
243
244 if (defined($opt_password)) {
245 if ($opt_password !~ m/^\$/) {
246 my $time = substr (Digest::SHA::sha1_base64 (time), 0, 8);
247 $opt_password = crypt(encode("utf8", $opt_password), "\$1\$$time\$");
248 };
249 } else {
250 $opt_password = '*';
251 }
252
253 if (-f $shadow) {
254 &$replacepw ($shadow, $user, $opt_password);
255 &$replacepw ($pwfile, $user, 'x');
256 } else {
257 &$replacepw ($pwfile, $user, $opt_password);
258 }
259}
260
4727bd09
DM
261my $randomize_crontab = sub {
262 my ($self, $conf) = @_;
263
264 my $rootdir = $self->{rootdir};
265
b5e62cd0
DM
266 my @files;
267 # Note: dir_glob_foreach() untaints filenames!
268 my $cron_dir = "$rootdir/etc/cron.d";
269 PVE::Tools::dir_glob_foreach($cron_dir, qr/[A-Z\-\_a-z0-9]+/, sub {
270 my ($name) = @_;
271 push @files, "$cron_dir/$name";
272 });
4727bd09
DM
273
274 my $crontab_fn = "$rootdir/etc/crontab";
275 unshift @files, $crontab_fn if -f $crontab_fn;
276
277 foreach my $filename (@files) {
278 my $data = PVE::Tools::file_get_contents($filename);
279 my $new = '';
280 foreach my $line (split(/\n/, $data)) {
281 # we only randomize minutes for root crontab entries
282 if ($line =~ m/^\d+(\s+\S+\s+\S+\s+\S+\s+\S+\s+root\s+\S.*)$/) {
283 my $rest = $1;
284 my $min = int(rand()*59);
285 $new .= "$min$rest\n";
286 } else {
287 $new .= "$line\n";
288 }
289 }
290 PVE::Tools::file_set_contents($filename, $new);
291 }
292};
293
7ee31468
DM
294sub rewrite_ssh_host_keys {
295 my ($self, $conf) = @_;
296
297 my $rootdir = $self->{rootdir};
298
299 my $etc_ssh_dir = "$rootdir/etc/ssh";
300
301 return if ! -d $etc_ssh_dir;
302
303 my $keynames = {
304 rsa1 => 'ssh_host_key',
305 rsa => 'ssh_host_rsa_key',
306 dsa => 'ssh_host_dsa_key',
307 ecdsa => 'ssh_host_ecdsa_key',
308 ed25519 => 'ssh_host_ed25519_key',
309 };
310
311 my $hostname = $conf->{'lxc.utsname'} || 'localhost';
312 $hostname =~ s/\..*$//;
313
314 foreach my $keytype (keys %$keynames) {
315 my $basename = $keynames->{$keytype};
316 unlink "${etc_ssh_dir}/$basename";
317 unlink "${etc_ssh_dir}/$basename.pub";
318 print "Creating SSH host key '$basename' - this may take some time ...\n";
319 my $cmd = ['ssh-keygen', '-q', '-f', "${etc_ssh_dir}/$basename", '-t', $keytype,
320 '-N', '', '-C', "root\@$hostname"];
321 PVE::Tools::run_command($cmd);
322 }
323}
324
d66768a2 325sub pre_start_hook {
633a7bd8 326 my ($self, $conf) = @_;
d66768a2 327
633a7bd8
DM
328 $self->setup_init($conf);
329 $self->setup_network($conf);
330 $self->set_hostname($conf);
331 $self->set_dns($conf);
d66768a2
DM
332
333 # fixme: what else ?
334}
335
336sub post_create_hook {
633a7bd8 337 my ($self, $conf, $root_password) = @_;
d66768a2 338
142444d5 339 $self->template_fixup($conf);
4727bd09
DM
340
341 &$randomize_crontab($self, $conf);
342
633a7bd8
DM
343 $self->set_user_password($conf, 'root', $root_password);
344 $self->setup_init($conf);
345 $self->setup_network($conf);
346 $self->set_hostname($conf);
347 $self->set_dns($conf);
7ee31468 348 $self->rewrite_ssh_host_keys($conf);
168d6b07 349
55fa4e09 350 # fixme: what else ?
1c7f4f65
DM
351}
352
3531;