Handle hostnames with upper-case letters
[webmin.git] / cluster-usermin / upgrade.cgi
1 #!/usr/local/bin/perl
2 # upgrade.cgi
3 # Download usermin and upgrade all managed servers of compatible types
4
5 require './cluster-usermin-lib.pl';
6 &foreign_require("proc", "proc-lib.pl");
7 &foreign_require("webmin", "webmin-lib.pl");
8 &foreign_require("webmin", "gnupg-lib.pl");
9 &ReadParseMime();
10
11 &ui_print_unbuffered_header(undef, $text{'upgrade_title'}, "");
12
13 # Save this CGI from being killed by the upgrade
14 $SIG{'TERM'} = 'IGNORE';
15
16 if ($in{'source'} == 0) {
17         # from local file
18         &error_setup(&usermin::text('upgrade_err1', $in{'file'}));
19         $file = $in{'file'};
20         if (!(-r $file)) { &inst_error($usermin::text{'upgrade_efile'}); }
21         }
22 elsif ($in{'source'} == 1) {
23         # from uploaded file
24         &error_setup($usermin::text{'upgrade_err2'});
25         $file = &tempname();
26         $need_unlink = 1;
27         if ($no_upload) {
28                 &inst_error($usermin::text{'upgrade_ebrowser'});
29                 }
30         open(MOD, ">$file");
31         print MOD $in{'upload'};
32         close(MOD);
33         }
34 elsif ($in{'source'} == 2) {
35         # find latest version at www.usermin.com by looking at index page
36         &error_setup($usermin::text{'upgrade_err3'});
37         $file = &tempname();
38         &http_download($usermin::update_host, $usermin::update_port, '/index6.html', $file, \$error);
39         $error && &inst_error($error);
40         open(FILE, $file);
41         while(<FILE>) {
42                 if (/usermin-([0-9\.]+)\.tar\.gz/) {
43                         $site_version = $1;
44                         last;
45                         }
46                 }
47         close(FILE);
48         unlink($file);
49         if ($in{'mode'} eq 'rpm') {
50                 $progress_callback_url = "http://$usermin::update_host/download/rpm/usermin-$site_version-1.noarch.rpm";
51                 &http_download($usermin::update_host, $usermin::update_port,
52                   "/download/rpm/usermin-$site_version-1.noarch.rpm", $file,
53                   \$error, \&progress_callback);
54                 }
55         elsif ($in{'mode'} eq 'deb') {
56                 $progress_callback_url = "http://$usermin::update_host/download/deb/usermin_$site_version.deb";
57                 &http_download($usermin::update_host, $usermin::update_port,
58                   "/download/deb/usermin_$site_version.deb", $file,
59                   \$error, \&progress_callback);
60                 }
61         else {
62                 $progress_callback_url = "http://$usermin::update_host/download/usermin-$site_version.tar.gz";
63                 &http_download($usermin::update_host, $usermin::update_port,
64                   "/download/usermin-$site_version.tar.gz", $file,
65                   \$error, \&progress_callback);
66                 }
67         $error && &inst_error($error);
68         $need_unlink = 1;
69         }
70 elsif ($in{'source'} == 5) {
71         # Download from some URL
72         &error_setup(&usermin::text('upgrade_err5', $in{'url'}));
73         $file = &tempname();
74         $progress_callback_url = $in{'url'};
75         if ($in{'url'} =~ /^(http|https):\/\/([^\/]+)(\/.*)$/) {
76                 $ssl = $1 eq 'https';
77                 $host = $2; $page = $3; $port = $ssl ? 443 : 80;
78                 if ($host =~ /^(.*):(\d+)$/) { $host = $1; $port = $2; }
79                 &http_download($host, $port, $page, $file, \$error,
80                                \&progress_callback, $ssl);
81                 }
82         elsif ($in{'url'} =~ /^ftp:\/\/([^\/]+)(:21)?\/(.*)$/) {
83                 $host = $1; $ffile = $3;
84                 &ftp_download($host, $ffile, $file,
85                               \$error, \&progress_callback);
86                 }
87         else { &inst_error($usermin::text{'upgrade_eurl'}); }
88         $need_unlink = 1;
89         $error && &inst_error($error);
90         }
91
92 # Work out what kind of file we have (RPM or tar.gz)
93 if (`rpm -qp $file 2>&1` =~ /(^|\n)usermin-(\d+\.\d+)/) {
94         # Looks like a usermin RPM
95         $mode = "rpm";
96         $version = $2;
97         }
98 elsif (`dpkg --info $file 2>&1` =~ /Package:\s+usermin-(\d+\.\d+)/) {
99         # Looks like a Usermin Debian package
100         $mode = "deb";
101         $version = $2;
102         }
103 else {
104         # Check if it is a usermin tar.gz file
105         open(TAR, "gunzip -c $file | tar tf - 2>&1 |");
106         while(<TAR>) {
107                 s/\r|\n//g;
108                 if (/^usermin-([0-9\.]+)\//) {
109                         $version = $1;
110                         }
111                 if (/^webmin-([0-9\.]+)\//) {
112                         $webmin_version = $1;
113                         }
114                 if (/^[^\/]+\/(\S+)$/) {
115                         $hasfile{$1}++;
116                         }
117                 if (/^(usermin-([0-9\.]+)\/[^\/]+)$/) {
118                         push(@topfiles, $1);
119                         }
120                 elsif (/^usermin-[0-9\.]+\/([^\/]+)\//) {
121                         $intar{$1}++;
122                         }
123                 }
124         close(TAR);
125         if ($webmin_version) {
126                 &inst_error(&usermin::text('upgrade_ewebmin',
127                                            $webmin_version));
128                 }
129         if (!$version) {
130                 if ($hasfile{'module.info'}) {
131                         &inst_error(&usermin::text('upgrade_emod', 'index.cgi'));
132                         }
133                 else {
134                         &inst_error($usermin::text{'upgrade_etar'});
135                         }
136                 }
137         $mode = "";
138         }
139
140 # gunzip the file if needed
141 open(FILE, $file);
142 read(FILE, $two, 2);
143 close(FILE);
144 if ($two eq "\037\213") {
145         if (!&has_command("gunzip")) {
146                 &inst_error($usermin::text{'upgrade_egunzip'});
147                 }
148         $newfile = &tempname();
149         $out = `gunzip -c $file 2>&1 >$newfile`;
150         if ($?) {
151                 unlink($newfile);
152                 &inst_error(&usermin::text('upgrade_egzip', "<tt>$out</tt>"));
153                 }
154         unlink($file) if ($need_unlink);
155         $need_unlink = 1;
156         $file = $newfile;
157         }
158
159 # Setup error handler for down hosts
160 sub inst_error
161 {
162 $inst_error_msg = join("", @_);
163 }
164 &remote_error_setup(\&inst_error);
165
166 # Build list of selected hosts
167 @hosts = &list_usermin_hosts();
168 @servers = &list_servers();
169 if ($in{'server'} == -2) {
170         # Upgrade servers know to run older versions?
171         @hosts = grep { $_->{'version'} < $version } @hosts;
172         print "<b>",&text('upgrade_header3', $version),"</b><p>\n";
173         }
174 elsif ($in{'server'} =~ /^group_(.*)/) {
175         # Upgrade members of some group
176         local ($group) = grep { $_->{'name'} eq $1 }
177                               &servers::list_all_groups(\@servers);
178         @hosts = grep { local $hid = $_->{'id'};
179                         local ($s) = grep { $_->{'id'} == $hid } @servers;
180                         &indexof($s->{'host'}, @{$group->{'members'}}) >= 0 }
181                       @hosts;
182         print "<b>",&text('upgrade_header4', $group->{'name'}),"</b><p>\n";
183         }
184 elsif ($in{'server'} != -1) {
185         # Upgrade one host
186         @hosts = grep { $_->{'id'} == $in{'server'} } @hosts;
187         local ($s) = grep { $_->{'id'} == $hosts[0]->{'id'} } @servers;
188         print "<b>",&text('upgrade_header2', &server_name($s)),"</b><p>\n";
189         }
190 else {
191         # Upgrading every host
192         print "<p><b>",&text('upgrade_header'),"</b><p>\n";
193         }
194
195 # Run the install
196 $p = 0;
197 foreach $h (@hosts) {
198         local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers;
199
200         local ($rh = "READ$p", $wh = "WRITE$p");
201         pipe($rh, $wh);
202         select($wh); $| = 1; select(STDOUT);
203         if (!fork()) {
204                 # Do the install in a subprocess
205                 close($rh);
206
207                 if (!$s->{'fast'} && $s->{'id'} != 0) {
208                         print $wh &serialise_variable($text{'upgrade_efast'});
209                         exit;
210                         }
211                 &remote_foreign_require($s->{'host'}, "usermin",
212                                                       "usermin-lib.pl");
213                 if ($inst_error_msg) {
214                         # Failed to contact host ..
215                         print $wh &serialise_variable($inst_error_msg);
216                         exit;
217                         }
218
219                 # Check the remote host's version
220                 local $rver = &remote_foreign_call($s->{'host'}, "usermin",
221                                                    "get_usermin_version");
222                 if ($version == $rver) {
223                         print $wh &serialise_variable(
224                                 &usermin::text('upgrade_elatest', $version));
225                         exit;
226                         }
227                 elsif ($version <= $rver) {
228                         print $wh &serialise_variable(
229                                 &usermin::text('upgrade_eversion', $version));
230                         exit;
231                         }
232
233                 # Check the install type on the remote host
234                 local $rmode = &remote_eval($s->{'host'}, "usermin",
235                     "&get_usermin_miniserv_config(\\\%miniserv); chop(\$m = `cat \$miniserv{'root'}/install-type`); \$m");
236                 if ($rmode ne $mode) {
237                         print $wh &serialise_variable(
238                                 &text('upgrade_emode',
239                                       $text{'upgrade_mode_'.$rmode},
240                                       $text{'upgrade_mode_'.$mode}));
241                         exit;
242                         }
243
244                 # Get the file to the server somehow
245                 local $rfile;
246                 local $host_need_unlink = 1;
247                 if (!$s->{'id'}) {
248                         # This host, so we already have the file
249                         $rfile = $file;
250                         $host_need_unlink = 0;
251                         }
252                 elsif ($in{'source'} == 0) {
253                         # Is the file the same on remote? (like if we have NFS)
254                         local @st = stat($file);
255                         local $rst = &remote_eval($s->{'host'}, "usermin",
256                                                   "[ stat('$file') ]");
257                         local @rst = @$rst;
258                         if (@st && @rst && $st[7] == $rst[7] &&
259                             $st[9] == $rst[9]) {
260                                 # File is the same! No need to download
261                                 $rfile = $file;
262                                 $host_need_unlink = 0;
263                                 }
264                         else {
265                                 # Need to copy the file across :(
266                                 $rfile = &remote_write(
267                                         $s->{'host'}, $file);
268                                 }
269                         }
270                 else {
271                         # Need to copy the file across :(
272                         $rfile = &remote_write($s->{'host'}, $file);
273                         }
274
275                 # Do the install ..
276                 if ($mode eq "rpm") {
277                         # Can just run RPM command
278                         # XXX doesn't actually check output!
279                         &remote_eval($s->{'host'}, "usermin", "system(\"rpm --import \$root_directory/webmin/jcameron-key.asc >/dev/null 2>&1\")");
280                         ($out, $ex) = &remote_eval($s->{'host'}, "usermin", "\$out = `rpm -U --ignoreos --ignorearch '$rfile' >/dev/null 2>&1 </dev/null`; (\$out, \$?)");
281                         &remote_eval($s->{'host'}, "usermin",
282                                      "unlink('$rfile')")
283                                 if ($host_need_unlink);
284                         if ($ex) {
285                                 print $wh &serialise_variable(
286                                         "<pre>$out</pre>");
287                                 exit;
288                                 }
289                         }
290                 elsif ($mode eq "deb") {
291                         # Can just run dpkg command
292                         ($out, $ex) = &remote_eval($s->{'host'}, "usermin", "\$out = `dpkg --install '$rfile' >/dev/null 2>&1 </dev/null`; (\$out, \$?)");
293                         &remote_eval($s->{'host'}, "usermin","unlink('$rfile')")
294                                 if ($host_need_unlink);
295                         if ($ex) {
296                                 print $wh &serialise_variable(
297                                         "<pre>$out</pre>");
298                                 exit;
299                                 }
300                         }
301                 else {
302                         # Get the original install directory
303                         local $rdir = &remote_eval($s->{'host'}, "usermin",
304                             "chop(\$m = `cat \$config{'usermin_dir'}/install-dir`); \$m");
305                         if ($rdir) {
306                                 # Extract tar.gz in temporary location first
307                                 $extract = &remote_foreign_call($s->{'host'}, "usermin", "tempname");
308                                 &remote_eval($s->{'host'}, "usermin", "mkdir('$extract', 0755)");
309                                 }
310                         else {
311                                 # Extract next to original dir
312                                 $oldroot = &remote_eval($s->{'host'}, "usermin", "\$root_directory");
313                                 $extract = "$oldroot/..";
314                                 }
315
316                         # Actually unpack the tar file
317                         local ($out, $ex) = &remote_eval($s->{'host'}, "usermin", "\$out = `cd '$extract' ; tar xf '$rfile' 2>&1 >/dev/null`; (\$out, \$?)");
318                         if ($ex) {
319                                 print $wh &serialise_variable(
320                                         "<pre>$out</pre>");
321                                 exit;
322                                 }
323
324                         # Delete the original tar.gz
325                         &remote_eval($s->{'host'}, "usermin", "unlink('$rfile')")
326                                 if ($host_need_unlink);
327
328                         # Run setup.sh in the extracted directory
329                         $setup = $rdir ? "./setup.sh '$rdir'" : "./setup.sh";
330                         ($out, $ex) = &remote_eval($s->{'host'}, "usermin",
331                                 "\$SIG{'TERM'} = 'IGNORE';
332                                  \$ENV{'config_dir'} = \$config{'usermin_dir'};
333                                  \$ENV{'webmin_upgrade'} = 1;
334                                  \$ENV{'autothird'} = 1;
335                                  \$out = `(cd $extract/usermin-$version && $setup) </dev/null 2>&1 | tee /tmp/.webmin/usermin-setup.out`;
336                                  (\$out, \$?)");
337                         if ($out !~ /success/i) {
338                                 print $wh &serialise_variable(
339                                         "<pre>$out</pre>");
340                                 exit;
341                                 }
342                         if ($rdir) {
343                                 # Can delete the temporary source directory
344                                 &remote_eval($s->{'host'}, "usermin",
345                                              "system(\"rm -rf \'$extract\'\")");
346                                 }
347                         elsif ($in{'delete'}) {
348                                 # Can delete the old root directory
349                                 &remote_eval($s->{'host'}, "usermin",
350                                              "system(\"rm -rf \'$oldroot\'\")");
351                                 }
352                         }
353
354                 # Force an RPC re-connect to new version
355                 &remote_finished();
356                 &remote_foreign_require($s->{'host'}, "usermin",
357                                         "usermin-lib.pl");
358                 if ($inst_error_msg) {
359                         # Failed to contact host ..
360                         print $wh &serialise_variable(
361                                 &text('upgrade_ereconn', $inst_error_msg));
362                         exit;
363                         }
364                 &remote_foreign_require($s->{'host'}, "acl", "acl-lib.pl");
365
366                 # Update local version number and module lists
367                 $h->{'version'} = $version;
368                 local @mods = &remote_foreign_call($s->{'host'},
369                                 "usermin", "list_modules");
370                 @mods = grep { !$_->{'clone'} } @mods;
371                 $h->{'modules'} = \@mods;
372                 local @themes = &remote_foreign_call($s->{'host'},
373                                  "usermin", "list_themes");
374                 $h->{'themes'} = \@themes;
375                 &save_usermin_host($h);
376
377                 print $wh &serialise_variable("");
378                 close($wh);
379                 exit;
380                 }
381         close($wh);
382         $p++;
383         }
384
385 # Get back all the results
386 $p = 0;
387 foreach $h (@hosts) {
388         local $rh = "READ$p";
389         local $line = <$rh>;
390         close($rh);
391         local $rv = &unserialise_variable($line);
392
393         local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers;
394         local $d = &server_name($s);
395
396         if (!$line) {
397                 print &text('upgrade_failed', $d, "Unknown reason"),"<br>\n";
398                 }
399         elsif ($rv) {
400                 print &text('upgrade_failed', $d, $rv),"<br>\n";
401                 }
402         else {
403                 print &text('upgrade_ok',
404                             $text{'upgrade_mode_'.$mode}, $d),"<br>\n";
405                 }
406         $p++;
407         }
408 unlink($file) if ($need_unlink);
409 print "<p><b>$text{'upgrade_done'}</b><p>\n";
410
411 &remote_finished();
412 &ui_print_footer("", $text{'index_return'});
413
414 sub inst_error
415 {
416 unlink($file) if ($need_unlink);
417 print "<br><b>$whatfailed : $_[0]</b> <p>\n";
418 &ui_print_footer("", $text{'index_return'});
419 exit;
420 }
421