Handle hostnames with upper-case letters
[webmin.git] / makemoduledeb.pl
1 #!/usr/bin/perl
2 # makemoduledeb.pl
3 # Create a Debian package for a webmin or usermin module or theme
4
5 use POSIX;
6
7 $licence = "BSD";
8 $email = "Jamie Cameron <jcameron\@webmin.com>";
9 $target_dir = "/tmp";
10
11 $tmp_dir = "/tmp/debian-module";
12 $debian_dir = "$tmp_dir/DEBIAN";
13 $control_file = "$debian_dir/control";
14 $preinstall_file = "$debian_dir/preinst";
15 $postinstall_file = "$debian_dir/postinst";
16 $preuninstall_file = "$debian_dir/prerm";
17 $postuninstall_file = "$debian_dir/postrm";
18 $copyright_file = "$debian_dir/copyright";
19 $changelog_file = "$debian_dir/changelog";
20 $files_file = "$debian_dir/files";
21
22 -r "/etc/debian_version" || die "makemoduledeb.pl must be run on Debian";
23
24 # Parse command-line args
25 while(@ARGV) {
26         local $a = shift(@ARGV);
27         if ($a eq "--force-theme") {
28                 $force_theme = 1;
29                 }
30         elsif ($a eq "--licence" || $a eq "--license") {
31                 $licence = shift(@ARGV);
32                 }
33         elsif ($a eq "--email") {
34                 $email = shift(@ARGV);
35                 }
36         elsif ($a eq "--url") {
37                 $url = shift(@ARGV);
38                 }
39         elsif ($a eq "--upstream") {
40                 $upstream = shift(@ARGV);
41                 }
42         elsif ($a eq "--deb-depends") {
43                 $rpmdepends = 1;
44                 }
45         elsif ($a eq "--no-prefix") {
46                 $no_prefix = 1;
47                 }
48         elsif ($a eq "--usermin") {
49                 $force_usermin = 1;
50                 }
51         elsif ($a eq "--target-dir") {
52                 $target_dir = shift(@ARGV);
53                 }
54         elsif ($a eq "--dir") {
55                 $final_mod = shift(@ARGV);
56                 }
57         elsif ($a eq "--allow-overwrite") {
58                 $allow_overwrite = 1;
59                 }
60         elsif ($a eq "--dsc-file") {
61                 $dsc_file = shift(@ARGV);
62                 }
63         elsif ($a =~ /^\-\-/) {
64                 print STDERR "Unknown option $a\n";
65                 exit(1);
66                 }
67         else {
68                 if (!defined($dir)) {
69                         $dir = $a;
70                         }
71                 else {
72                         $ver = $a;
73                         }
74                 }
75         }
76
77 # Validate args
78 if (!$dir) {
79         print STDERR "usage: makemoduledeb.pl [--force-theme]\n";
80         print STDERR "                        [--deb-depends]\n";
81         print STDERR "                        [--no-prefix]\n";
82         print STDERR "                        [--licence name]\n";
83         print STDERR "                        [--email 'name <address>']\n";
84         print STDERR "                        [--upstream 'name <address>']\n";
85         print STDERR "                        [--provides provides]\n";
86         print STDERR "                        [--usermin]\n";
87         print STDERR "                        [--target-dir directory]\n";
88         print STDERR "                        [--dir directory-in-package]\n";
89         print STDERR "                        [--allow-overwrite]\n";
90         print STDERR "                        [--dsc-file file.dsc]\n";
91         print STDERR "                        <module> [version]\n";
92         exit(1);
93         }
94 chop($par = `dirname $dir`);
95 chop($source_mod = `basename $dir`);
96 $source_dir = "$par/$source_mod";
97 $mod = $final_mod || $source_mod;
98 if ($mod eq "." || $mod eq "..") {
99         die "directory must be an actual directory (module) name, not \"$mod\"";
100         }
101
102 # Is this actually a module or theme directory?
103 -d $source_dir || die "$source_dir is not a directory";
104 if (&read_file("$source_dir/module.info", \%minfo) && $minfo{'desc'}) {
105         $depends = join(" ", map { s/\/[0-9\.]+//; $_ }
106                                 grep { !/^[0-9\.]+$/ }
107                                   split(/\s+/, $minfo{'depends'}));
108         if ($minfo{'usermin'} && (!$minfo{'webmin'} || $force_usermin)) {
109                 $prefix = "usermin-";
110                 $desc = "Usermin module for '$minfo{'desc'}'";
111                 $product = "usermin";
112                 }
113         else {
114                 $prefix = "webmin-";
115                 $desc = "Webmin module for '$minfo{'desc'}'";
116                 $product = "webmin";
117                 }
118         $iver = $minfo{'version'};
119         $post_config = 1;
120         }
121 elsif (&read_file("$source_dir/theme.info", \%tinfo) && $tinfo{'desc'}) {
122         if ($tinfo{'usermin'} && (!$tinfo{'usermin'} || $force_usermin)) {
123                 $prefix = "usermin-";
124                 $desc = "Usermin theme '$tinfo{'desc'}'";
125                 $product = "usermin";
126                 }
127         else {
128                 $prefix = "webmin-";
129                 $desc = "Webmin theme '$tinfo{'desc'}'";
130                 $product = "webmin";
131                 }
132         $iver = $tinfo{'version'};
133         $istheme = 1;
134         $post_config = 0;
135         }
136 else {
137         die "$source_dir does not appear to be a webmin module or theme";
138         }
139 $prefix = "" if ($no_prefix);
140 $usr_dir = "$tmp_dir/usr/share/$product";
141 $ucproduct = ucfirst($product);
142 $ver ||= $iver;         # Use module.info version, or 1
143 $ver ||= 1;
144 $upstream ||= $email;
145
146 # Create the base directories
147 system("rm -rf $tmp_dir");
148 mkdir($tmp_dir, 0755);
149 chmod(0755, $tmp_dir);
150 mkdir($debian_dir, 0755);
151 chmod(0755, $debian_dir);
152 system("mkdir -p $usr_dir");
153
154 # Copy the directory to a temp directory
155 system("cp -r -p -L $source_dir $usr_dir/$mod");
156 system("echo deb >$usr_dir/$mod/install-type");
157 system("cd $usr_dir && chmod -R og-w .");
158 if ($< == 0) {
159         system("cd $usr_dir && chown -R root:bin .");
160         }
161 $size = int(`du -sk $tmp_dir`);
162 system("find $usr_dir -name .svn | xargs rm -rf");
163 system("find $usr_dir -name .xvpics | xargs rm -rf");
164 system("find $usr_dir -name '*.bak' | xargs rm -rf");
165 system("find $usr_dir -name '*~' | xargs rm -rf");
166 system("find $usr_dir -name '*.rej' | xargs rm -rf");
167 system("find $usr_dir -name core | xargs rm -rf");
168
169 # Fix up Perl paths
170 system("(find $usr_dir -name '*.cgi' ; find $usr_dir -name '*.pl') | perl -ne 'chop; open(F,\$_); \@l=<F>; close(F); \$l[0] = \"#\!/usr/bin/perl\$1\n\" if (\$l[0] =~ /#\!\\S*perl\\S*(.*)/); open(F,\">\$_\"); print F \@l; close(F)'");
171 system("(find $usr_dir -name '*.cgi' ; find $usr_dir -name '*.pl') | xargs chmod +x");
172
173 # Build list of dependencies on other Debian packages, for inclusion as a
174 # Requires: header
175 @rdeps = ( "base", "perl", $product );
176 if ($rpmdepends) {
177         foreach $d (split(/\s+/, $minfo{'depends'})) {
178                 local ($dwebmin, $dmod, $dver);
179                 if ($d =~ /^[0-9\.]+$/) {
180                         # Depends on a version of Webmin
181                         $dwebmin = $d;
182                         }
183                 elsif ($d =~ /^(\S+)\/([0-9\.]+)$/) {
184                         # Depends on some version of a module
185                         $dmod = $1;
186                         $dver = $2;
187                         }
188                 else {
189                         # Depends on any version of a module
190                         $dmod = $d;
191                         }
192
193                 # If the module is part of Webmin, we don't need to depend on it
194                 if ($dmod) {
195                         local %dinfo;
196                         &read_file("$dmod/module.info", \%dinfo);
197                         next if ($dinfo{'longdesc'});
198                         }
199                 push(@rdeps, $dwebmin ? ("$product (>= $dwebmin)") :
200                              $dver ? ("$prefix$dmod (>= $dver)") :
201                                      ($prefix.$dmod));
202                 }
203         }
204 $rdeps = join(", ", @rdeps);
205
206 # Create the control file
207 open(CONTROL, ">$control_file");
208 print CONTROL <<EOF;
209 Package: $prefix$mod
210 Version: $ver
211 Section: admin
212 Priority: optional
213 Architecture: all
214 Essential: no
215 Depends: $rdeps
216 Pre-Depends: bash, perl
217 Installed-Size: $size
218 Maintainer: $email
219 Provides: $prefix$mod
220 Description: $desc
221 EOF
222 close(CONTROL);
223
224 # Create the copyright file
225 $nowstr = strftime("%a, %d %b %Y %H:%M:%S %z", localtime(time()));
226 open(COPY, ">$copyright_file");
227 print COPY "This package was debianized by $email on\n";
228 print COPY "$nowstr.\n";
229 print COPY "\n";
230 if ($url) {
231         print COPY "It was downloaded from: $url\n";
232         print COPY "\n";
233         }
234 print COPY "Upstream author: $upstream\n";
235 print COPY "\n";
236 print COPY "Copyright: $licence\n";
237 close(COPY);
238
239 # Read the module's CHANGELOG file
240 $changes = { };
241 $f = "$usr_dir/$mod/CHANGELOG";
242 if (-r $f) {
243         # Read its change log file
244         local $inversion;
245         open(LOG, $f);
246         while(<LOG>) {
247                 s/\r|\n//g;
248                 if (/^----\s+Changes\s+since\s+(\S+)\s+----/) {
249                         $inversion = $1;
250                         }
251                 elsif ($inversion && /\S/) {
252                         push(@{$changes->{$inversion}}, $_);
253                         }
254                 }
255         close(LOG);
256         }
257
258 # Create the changelog file from actual changes
259 if (%$changes) {
260         open(CHANGELOG, ">$changelog_file");
261         foreach $v (sort { $a <=> $b } (keys %$changes)) {
262                 if ($ver > $v && sprintf("%.2f0", $ver) == $v) {
263                         $forv = $ver;
264                         }
265                 else {
266                         $forv = sprintf("%.2f0", $v+0.01);
267                         }
268                 print CHANGELOG "$prefix$mod ($forv) stable; urgency=low\n";
269                 print CHANGELOG "\n";
270                 foreach $c (@{$changes->{$v}}) {
271                         @lines = &wrap_lines($c, 65);
272                         print CHANGELOG " * $lines[0]\n";
273                         foreach $l (@lines[1 .. $#lines]) {
274                                 print CHANGELOG "   $l\n";
275                                 }
276                         }
277                 print CHANGELOG "\n";
278                 print CHANGELOG "-- $email\n";
279                 print CHANGELOG "\n";
280                 }
281         }
282 close(CHANGELOG);
283
284 # Create the pre-install script, which checks if Webmin is installed
285 open(SCRIPT, ">$preinstall_file");
286 print SCRIPT <<EOF;
287 #!/bin/sh
288 if [ ! -r /etc/$product/config -o ! -d /usr/share/$product ]; then
289         echo "$ucproduct does not appear to be installed on your system."
290         echo "This package cannot be installed unless the Debian version of $ucproduct"
291         echo "is installed first."
292         exit 1
293 fi
294 if [ "$depends" != "" -a "$debdepends" != 1 ]; then
295         # Check if depended webmin/usermin modules are installed
296         for d in $depends; do
297                 if [ ! -r /usr/share/$product/\$d/module.info ]; then
298                         echo "This $ucproduct module depends on the module \$d, which is"
299                         echo "not installed on your system."
300                         exit 1
301                 fi
302         done
303 fi
304 # Check if this module is already installed
305 if [ -d /usr/share/$product/$mod -a "\$1" != "upgrade" -a "$allow_overwrite" != "1" ]; then
306         echo "This $ucproduct module is already installed on your system."
307         exit 1
308 fi
309 EOF
310 close(SCRIPT);
311 system("chmod 755 $preinstall_file");
312
313 # Create the post-install script
314 open(SCRIPT, ">$postinstall_file");
315 print SCRIPT <<EOF;
316 #!/bin/sh
317 if [ "$post_config" = "1" ]; then
318         # Copy config file to /etc/webmin or /etc/usermin
319         os_type=`grep "^os_type=" /etc/$product/config | sed -e 's/os_type=//g'`
320         os_version=`grep "^os_version=" /etc/$product/config | sed -e 's/os_version=//g'`
321         /usr/bin/perl /usr/share/$product/copyconfig.pl \$os_type \$os_version /usr/share/$product /etc/$product $mod
322
323         # Update the ACL for the root user, or the first user in the ACL
324         grep "^root:" /etc/$product/webmin.acl >/dev/null
325         if [ "\$?" = "0" ]; then
326                 user=root
327         else
328                 user=`head -1 /etc/$product/webmin.acl | cut -f 1 -d :`
329         fi
330         mods=`grep \$user: /etc/$product/webmin.acl | cut -f 2 -d :`
331         echo \$mods | grep " $mod" >/dev/null
332         if [ "\$?" != "0" ]; then
333                 grep -v ^\$user: /etc/$product/webmin.acl > /tmp/webmin.acl.tmp
334                 echo \$user: \$mods $mod > /etc/$product/webmin.acl
335                 cat /tmp/webmin.acl.tmp >> /etc/$product/webmin.acl
336                 rm -f /tmp/webmin.acl.tmp
337         fi
338 fi
339 if [ "$force_theme" != "" -a "$istheme" = "1" ]; then
340         # Activate this theme
341         grep -v "^preroot=" /etc/$product/miniserv.conf >/etc/$product/miniserv.conf.tmp
342         (cat /etc/$product/miniserv.conf.tmp ; echo preroot=$mod) > /etc/$product/miniserv.conf
343         rm -f /etc/$product/miniserv.conf.tmp
344         grep -v "^theme=" /etc/$product/config >/etc/$product/config.tmp
345         (cat /etc/$product/config.tmp ; echo theme=$mod) > /etc/$product/config
346         rm -f /etc/$product/config.tmp
347         (/etc/$product/stop && /etc/$product/start) >/dev/null 2>&1
348 fi
349 rm -f /etc/$product/module.infos.cache
350
351 # Run post-install function
352 if [ "$product" = "webmin" ]; then
353         cd /usr/share/$product
354         WEBMIN_CONFIG=/etc/$product WEBMIN_VAR=/var/$product /usr/share/$product/run-postinstalls.pl $mod
355 fi
356
357 # Run post-install shell script
358 if [ -r "/usr/share/$product/$mod/postinstall.sh" ]; then
359         cd /usr/share/$product
360         WEBMIN_CONFIG=/etc/$product WEBMIN_VAR=/var/$product /usr/share/$product/$mod/postinstall.sh
361 fi
362 EOF
363 close(SCRIPT);
364 system("chmod 755 $postinstall_file");
365
366 # Create the pre-uninstall script
367 open(SCRIPT, ">$preuninstall_file");
368 print SCRIPT <<EOF;
369 #!/bin/sh
370 # De-activate this theme, if in use and if we are not upgrading
371 if [ "$istheme" = "1" -a "\$1" != "upgrade" ]; then
372         grep "^preroot=$mod" /etc/$product/miniserv.conf >/dev/null
373         if [ "\$?" = "0" ]; then
374                 grep -v "^preroot=$mod" /etc/$product/miniserv.conf >/etc/$product/miniserv.conf.tmp
375                 (cat /etc/$product/miniserv.conf.tmp) > /etc/$product/miniserv.conf
376                 rm -f /etc/$product/miniserv.conf.tmp
377                 grep -v "^theme=$mod" /etc/$product/config >/etc/$product/config.tmp
378                 (cat /etc/$product/config.tmp) > /etc/$product/config
379                 rm -f /etc/$product/config.tmp
380                 (/etc/$product/stop && /etc/$product/start) >/dev/null 2>&1
381         fi
382 fi
383 # Run the pre-uninstall script, if we are not upgrading
384 if [ "$product" = "webmin" -a "\$1" = "0" -a -r "/usr/share/$product/$mod/uninstall.pl" ]; then
385         cd /usr/share/$product
386         WEBMIN_CONFIG=/etc/$product WEBMIN_VAR=/var/$product /usr/share/$product/run-uninstalls.pl $mod
387 fi
388 /bin/true
389 EOF
390 close(SCRIPT);
391 system("chmod 755 $preuninstall_file");
392
393 # Run the actual build command
394 system("fakeroot dpkg --build $tmp_dir $target_dir/${prefix}${mod}_${ver}_all.deb") &&
395         die "dpkg failed";
396 print "Wrote $target_dir/${prefix}${mod}_${ver}_all.deb\n";
397
398 # Create the .dsc file, if requested
399 if ($dsc_file) {
400         # Create the .diff file, which just contains the debian directory
401         $diff_file = $dsc_file;
402         $diff_file =~ s/[^\/]+$//; $diff_file .= "$prefix$mod-$ver.diff";
403         $diff_orig_dir = "$tmp_dir/$prefix$mod-$ver-orig";
404         $diff_new_dir = "$tmp_dir/$prefix$mod-$ver";
405         mkdir($diff_orig_dir, 0755);
406         mkdir($diff_new_dir, 0755);
407         system("cp -r $debian_dir $diff_new_dir");
408         system("cd $tmp_dir && diff -r -N -u $prefix$mod-$ver-orig $prefix$mod-$ver >$diff_file");
409         $diffmd5 = `md5sum $diff_file`;
410         $diffmd5 =~ s/\s+.*\n//g;
411         @diffst = stat($diff_file);
412
413         # Create a tar file of the module directory
414         $tar_file = $dsc_file;
415         $tar_file =~ s/[^\/]+$//; $tar_file .= "$prefix$mod-$ver.tar.gz";
416         system("cd $par ; tar czf $tar_file $source_mod");
417         $md5 = `md5sum $tar_file`;
418         $md5 =~ s/\s+.*\n//g;
419         @st = stat($tar_file);
420
421         # Finally create the .dsc
422         open(DSC, ">$dsc_file");
423         print DSC <<EOF;
424 Format: 1.0
425 Source: $prefix$mod
426 Version: $ver
427 Binary: $prefix$mod
428 Maintainer: $email
429 Architecture: all
430 Standards-Version: 3.6.1
431 Build-Depends-Indep: debhelper (>= 4.1.16), debconf (>= 0.5.00), perl
432 Uploaders: Jamie Cameron <jcameron\@webmin.com>
433 Files:
434   $md5 $st[7] ${prefix}${mod}-$ver.tar.gz
435   $diffmd5 $diffst[7] ${prefix}${mod}-${ver}.diff
436 EOF
437         close(DSC);
438         }
439
440 # Clean up
441 system("rm -rf $tmp_dir");
442
443 # read_file(file, &assoc, [&order], [lowercase])
444 # Fill an associative array with name=value pairs from a file
445 sub read_file
446 {
447 open(ARFILE, $_[0]) || return 0;
448 while(<ARFILE>) {
449         s/\r|\n//g;
450         if (!/^#/ && /^([^=]+)=(.*)$/) {
451                 $_[1]->{$_[3] ? lc($1) : $1} = $2;
452                 push(@{$_[2]}, $1) if ($_[2]);
453                 }
454         }
455 close(ARFILE);
456 return 1;
457 }
458
459 # wrap_lines(text, width)
460 # Given a multi-line string, return an array of lines wrapped to
461 # the given width
462 sub wrap_lines
463 {
464 local @rv;
465 local $w = $_[1];
466 local $rest;
467 foreach $rest (split(/\n/, $_[0])) {
468         if ($rest =~ /\S/) {
469                 while($rest =~ /^(.{1,$w}\S*)\s*([\0-\377]*)$/) {
470                         push(@rv, $1);
471                         $rest = $2;
472                         }
473                 }
474         else {
475                 # Empty line .. keep as it is
476                 push(@rv, $rest);
477                 }
478         }
479 return @rv;
480 }
481
482