]>
Commit | Line | Data |
---|---|---|
7af97ad5 | 1 | package PVE::LXC::Setup; |
1c7f4f65 DM |
2 | |
3 | use strict; | |
4 | use warnings; | |
f08b2779 | 5 | use POSIX; |
a8e58e9c | 6 | use PVE::Tools; |
1c7f4f65 | 7 | |
7af97ad5 DM |
8 | use PVE::LXC::Setup::Debian; |
9 | use PVE::LXC::Setup::Ubuntu; | |
10 | use PVE::LXC::Setup::Redhat; | |
b0143ab1 | 11 | use PVE::LXC::Setup::Fedora; |
fa7cb12b | 12 | use PVE::LXC::Setup::SUSE; |
7af97ad5 | 13 | use PVE::LXC::Setup::ArchLinux; |
a8700492 | 14 | use PVE::LXC::Setup::Alpine; |
1c7f4f65 DM |
15 | |
16 | my $plugins = { | |
7af97ad5 DM |
17 | debian => 'PVE::LXC::Setup::Debian', |
18 | ubuntu => 'PVE::LXC::Setup::Ubuntu', | |
19 | redhat => 'PVE::LXC::Setup::Redhat', | |
b0143ab1 | 20 | fedora => 'PVE::LXC::Setup::Fedora', |
fa7cb12b | 21 | opensuse => 'PVE::LXC::Setup::SUSE', |
7af97ad5 | 22 | archlinux => 'PVE::LXC::Setup::ArchLinux', |
a8700492 | 23 | alpine => 'PVE::LXC::Setup::Alpine', |
1c7f4f65 DM |
24 | }; |
25 | ||
bdd4194c | 26 | my $autodetect_type = sub { |
5b4657d0 | 27 | my ($rootdir) = @_; |
a8e58e9c DM |
28 | |
29 | my $lsb_fn = "$rootdir/etc/lsb-release"; | |
30 | if (-f $lsb_fn) { | |
31 | my $data = PVE::Tools::file_get_contents($lsb_fn); | |
32 | if ($data =~ m/^DISTRIB_ID=Ubuntu$/im) { | |
33 | return 'ubuntu'; | |
34 | } | |
75c7928e DM |
35 | } |
36 | ||
37 | if (-f "$rootdir/etc/debian_version") { | |
bdd4194c | 38 | return "debian"; |
fa7cb12b WB |
39 | } elsif (-f "$rootdir/etc/SuSE-brand" || -f "$rootdir/etc/SuSE-release") { |
40 | return "opensuse"; | |
b0143ab1 WB |
41 | } elsif (-f "$rootdir/etc/fedora-release") { |
42 | return "fedora"; | |
c0eae401 DM |
43 | } elsif (-f "$rootdir/etc/redhat-release") { |
44 | return "redhat"; | |
c1d32b55 WB |
45 | } elsif (-f "$rootdir/etc/arch-release") { |
46 | return "archlinux"; | |
a8700492 JV |
47 | } elsif (-f "$rootdir/etc/alpine-release") { |
48 | return "alpine"; | |
bdd4194c DM |
49 | } |
50 | die "unable to detect OS disribution\n"; | |
51 | }; | |
52 | ||
1c7f4f65 | 53 | sub new { |
5b4657d0 | 54 | my ($class, $conf, $rootdir, $type) = @_; |
1c7f4f65 | 55 | |
5b4657d0 DM |
56 | die "no root directory\n" if !$rootdir || $rootdir eq '/'; |
57 | ||
5b3614f8 | 58 | my $self = bless { conf => $conf, rootdir => $rootdir}; |
1c7f4f65 | 59 | |
238b7e3e DM |
60 | if ($conf->{ostype} && $conf->{ostype} eq 'unmanaged') { |
61 | return $self; | |
62 | } elsif (!defined($type)) { | |
bdd4194c | 63 | # try to autodetect type |
5b4657d0 | 64 | $type = &$autodetect_type($rootdir); |
238b7e3e DM |
65 | my $expected_type = $conf->{ostype} || $type; |
66 | ||
67 | die "got unexpected ostype ($type != $expected_type)\n" | |
68 | if $type ne $expected_type; | |
bdd4194c | 69 | } |
238b7e3e | 70 | |
633a7bd8 | 71 | my $plugin_class = $plugins->{$type} || |
1c7f4f65 DM |
72 | "no such OS type '$type'\n"; |
73 | ||
23d928a1 WB |
74 | my $plugin = $plugin_class->new($conf, $rootdir); |
75 | $self->{plugin} = $plugin; | |
f08b2779 | 76 | $self->{in_chroot} = 0; |
23d928a1 WB |
77 | |
78 | # Cache some host files we need access to: | |
79 | $plugin->{host_resolv_conf} = PVE::INotify::read_file('resolvconf'); | |
c6a605f9 WB |
80 | |
81 | # pass on user namespace information: | |
82 | my ($id_map, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); | |
83 | if (@$id_map) { | |
84 | $plugin->{id_map} = $id_map; | |
85 | $plugin->{rootuid} = $rootuid; | |
86 | $plugin->{rootgid} = $rootgid; | |
87 | } | |
633a7bd8 | 88 | |
1c7f4f65 DM |
89 | return $self; |
90 | } | |
91 | ||
f08b2779 WB |
92 | sub protected_call { |
93 | my ($self, $sub) = @_; | |
94 | ||
238b7e3e DM |
95 | die "internal error" if !$self->{plugin}; |
96 | ||
f08b2779 WB |
97 | # avoid recursion: |
98 | return $sub->() if $self->{in_chroot}; | |
99 | ||
100 | my $rootdir = $self->{rootdir}; | |
101 | if (!-d "$rootdir/dev" && !mkdir("$rootdir/dev")) { | |
102 | die "failed to create temporary /dev directory: $!\n"; | |
103 | } | |
104 | ||
105 | my $child = fork(); | |
106 | die "fork failed: $!\n" if !defined($child); | |
107 | ||
108 | if (!$child) { | |
109 | # avoid recursive forks | |
110 | $self->{in_chroot} = 1; | |
111 | $self->{plugin}->{in_chroot} = 1; | |
112 | eval { | |
f08b2779 WB |
113 | chroot($rootdir) or die "failed to change root to: $rootdir: $!\n"; |
114 | chdir('/') or die "failed to change to root directory\n"; | |
115 | $sub->(); | |
116 | }; | |
117 | if (my $err = $@) { | |
797e12e8 | 118 | warn $err; |
f08b2779 WB |
119 | POSIX::_exit(1); |
120 | } | |
121 | POSIX::_exit(0); | |
122 | } | |
123 | while (waitpid($child, 0) != $child) {} | |
952de558 WB |
124 | if ($? != 0) { |
125 | my $method = (caller(1))[3]; | |
126 | die "error in setup task $method\n"; | |
127 | } | |
f08b2779 WB |
128 | } |
129 | ||
142444d5 DM |
130 | sub template_fixup { |
131 | my ($self) = @_; | |
132 | ||
238b7e3e DM |
133 | return if !$self->{plugin}; # unmanaged |
134 | ||
f08b2779 WB |
135 | my $code = sub { |
136 | $self->{plugin}->template_fixup($self->{conf}); | |
137 | }; | |
138 | $self->protected_call($code); | |
142444d5 DM |
139 | } |
140 | ||
1c7f4f65 | 141 | sub setup_network { |
55fa4e09 | 142 | my ($self) = @_; |
1c7f4f65 | 143 | |
238b7e3e DM |
144 | return if !$self->{plugin}; # unmanaged |
145 | ||
f08b2779 WB |
146 | my $code = sub { |
147 | $self->{plugin}->setup_network($self->{conf}); | |
148 | }; | |
149 | $self->protected_call($code); | |
1c7f4f65 DM |
150 | } |
151 | ||
152 | sub set_hostname { | |
153 | my ($self) = @_; | |
154 | ||
238b7e3e DM |
155 | return if !$self->{plugin}; # unmanaged |
156 | ||
f08b2779 WB |
157 | my $code = sub { |
158 | $self->{plugin}->set_hostname($self->{conf}); | |
159 | }; | |
160 | $self->protected_call($code); | |
1c7f4f65 DM |
161 | } |
162 | ||
c325b32f DM |
163 | sub set_dns { |
164 | my ($self) = @_; | |
165 | ||
238b7e3e DM |
166 | return if !$self->{plugin}; # unmanaged |
167 | ||
f08b2779 WB |
168 | my $code = sub { |
169 | $self->{plugin}->set_dns($self->{conf}); | |
170 | }; | |
171 | $self->protected_call($code); | |
c325b32f DM |
172 | } |
173 | ||
d66768a2 DM |
174 | sub setup_init { |
175 | my ($self) = @_; | |
176 | ||
238b7e3e DM |
177 | return if !$self->{plugin}; # unmanaged |
178 | ||
f08b2779 WB |
179 | my $code = sub { |
180 | $self->{plugin}->setup_init($self->{conf}); | |
181 | }; | |
182 | $self->protected_call($code); | |
d66768a2 DM |
183 | } |
184 | ||
168d6b07 DM |
185 | sub set_user_password { |
186 | my ($self, $user, $pw) = @_; | |
238b7e3e DM |
187 | |
188 | return if !$self->{plugin}; # unmanaged | |
189 | ||
f08b2779 WB |
190 | my $code = sub { |
191 | $self->{plugin}->set_user_password($self->{conf}, $user, $pw); | |
192 | }; | |
193 | $self->protected_call($code); | |
1c7f4f65 DM |
194 | } |
195 | ||
7ee31468 DM |
196 | sub rewrite_ssh_host_keys { |
197 | my ($self) = @_; | |
198 | ||
238b7e3e DM |
199 | return if !$self->{plugin}; # unmanaged |
200 | ||
797e12e8 WB |
201 | my $conf = $self->{conf}; |
202 | my $plugin = $self->{plugin}; | |
203 | my $rootdir = $self->{rootdir}; | |
204 | ||
feb7383b | 205 | return if ! -d "$rootdir/etc/ssh"; |
797e12e8 WB |
206 | |
207 | my $keynames = { | |
208 | rsa1 => 'ssh_host_key', | |
209 | rsa => 'ssh_host_rsa_key', | |
210 | dsa => 'ssh_host_dsa_key', | |
211 | ecdsa => 'ssh_host_ecdsa_key', | |
212 | ed25519 => 'ssh_host_ed25519_key', | |
213 | }; | |
214 | ||
215 | my $hostname = $conf->{hostname} || 'localhost'; | |
216 | $hostname =~ s/\..*$//; | |
217 | ||
218 | # Create temporary keys in /tmp on the host | |
219 | ||
220 | my $keyfiles = {}; | |
221 | foreach my $keytype (keys %$keynames) { | |
222 | my $basename = $keynames->{$keytype}; | |
223 | my $file = "/tmp/$$.$basename"; | |
224 | print "Creating SSH host key '$basename' - this may take some time ...\n"; | |
225 | my $cmd = ['ssh-keygen', '-q', '-f', $file, '-t', $keytype, | |
226 | '-N', '', '-C', "root\@$hostname"]; | |
227 | PVE::Tools::run_command($cmd); | |
39db5e47 WB |
228 | $keyfiles->{"/etc/ssh/$basename"} = [PVE::Tools::file_get_contents($file), 0600]; |
229 | $keyfiles->{"/etc/ssh/$basename.pub"} = [PVE::Tools::file_get_contents("$file.pub"), 0644]; | |
797e12e8 WB |
230 | unlink $file; |
231 | unlink "$file.pub"; | |
232 | } | |
233 | ||
234 | # Write keys out in a protected call | |
235 | ||
f08b2779 | 236 | my $code = sub { |
797e12e8 | 237 | foreach my $file (keys %$keyfiles) { |
39db5e47 | 238 | $plugin->ct_file_set_contents($file, @{$keyfiles->{$file}}); |
797e12e8 | 239 | } |
f08b2779 WB |
240 | }; |
241 | $self->protected_call($code); | |
7ee31468 DM |
242 | } |
243 | ||
d66768a2 DM |
244 | sub pre_start_hook { |
245 | my ($self) = @_; | |
246 | ||
238b7e3e DM |
247 | return if !$self->{plugin}; # unmanaged |
248 | ||
f08b2779 WB |
249 | my $code = sub { |
250 | # Create /fastboot to skip run fsck | |
251 | $self->{plugin}->ct_file_set_contents('/fastboot', ''); | |
2988dbbc | 252 | |
f08b2779 WB |
253 | $self->{plugin}->pre_start_hook($self->{conf}); |
254 | }; | |
255 | $self->protected_call($code); | |
d66768a2 DM |
256 | } |
257 | ||
258 | sub post_create_hook { | |
168d6b07 | 259 | my ($self, $root_password) = @_; |
1c7f4f65 | 260 | |
238b7e3e DM |
261 | return if !$self->{plugin}; # unmanaged |
262 | ||
f08b2779 WB |
263 | my $code = sub { |
264 | $self->{plugin}->post_create_hook($self->{conf}, $root_password); | |
265 | }; | |
266 | $self->protected_call($code); | |
797e12e8 | 267 | $self->rewrite_ssh_host_keys(); |
1c7f4f65 DM |
268 | } |
269 | ||
270 | 1; |