File Coverage

lib/Haineko/CLI/Setup.pm
Criterion Covered Total %
statement 69 183 37.7
branch 9 48 18.7
condition 9 16 56.2
subroutine 19 28 67.8
pod 4 6 66.6
total 110 281 39.1


line stmt bran cond sub pod time code
1             package Haineko::CLI::Setup;
2 1     1   6250 use parent 'Haineko::CLI';
  1         403  
  1         7  
3 1     1   99 use strict;
  1         2  
  1         50  
4 1     1   6 use warnings;
  1         2  
  1         34  
5 1     1   12 use IO::File;
  1         3  
  1         209  
6 1     1   7 use Try::Tiny;
  1         2  
  1         60  
7 1     1   6 use Fcntl qw(:flock);
  1         1  
  1         178  
8 1     1   7 use File::Copy;
  1         2  
  1         60  
9 1     1   6 use File::Temp;
  1         1  
  1         119  
10 1     1   6 use File::Basename qw/basename dirname/;
  1         2  
  1         81  
11 1     1   2472 use Haineko::CLI::Setup::Data;
  1         3  
  1         58  
12 1     1   6 use Path::Class::Dir;
  1         3  
  1         39  
13 1     1   1758 use MIME::Base64;
  1         1968  
  1         106  
14 1     1   6258 use Archive::Tar;
  1         237378  
  1         1647  
15              
16             sub options {
17             return {
18 3     3 0 14 'exec' => ( 1 << 0 ),
19             'force'=> ( 1 << 1 ),
20             };
21             }
22              
23             sub list {
24             return [
25 1     1 0 534 'bin/hainekoctl',
26             'etc/authinfo',
27             'etc/haineko.cf',
28             'etc/mailertable',
29             'etc/password',
30             'etc/recipients',
31             'etc/relayhosts',
32             'etc/sendermt',
33             'libexec/haineko.psgi',
34             ];
35             }
36              
37             sub make {
38 0     0 1 0 my $self = shift;
39 0         0 my $o = __PACKAGE__->options;
40              
41 0 0       0 return undef unless( $self->r & $o->{'exec'} );
42              
43 0         0 my $currentdir = qx(pwd); chomp $currentdir;
  0         0  
44 0         0 my $modulename = './lib/Haineko/CLI/Setup/Data.pm';
45 0         0 my $tempfolder = File::Temp->newdir;
46 0         0 my $subdirname = $tempfolder.'/haineko-setup-files';
47 0         0 my $tararchive = $subdirname.'.tar.gz';
48 0         0 my $setupfiles = [];
49 0         0 my $archiveobj = undef;
50              
51             try {
52 0     0   0 mkdir( $subdirname );
53 0         0 $self->p( 'Setup directory = '.$subdirname, 1 );
54             } catch {
55 0     0   0 $self->e( 'Failed to create setup directory: '.$subdirname );
56 0         0 };
57              
58 0         0 for my $e ( @{ __PACKAGE__->list } ) {
  0         0  
59 0         0 my $f = File::Basename::dirname $e;
60 0         0 my $g = Path::Class::File->new( $subdirname.'/'.$e );
61              
62             try {
63             # Copy files to a temporary directory
64 0 0   0   0 if( not -d $g->dir ) {
65 0         0 $g->dir->mkpath;
66 0         0 $self->p( '[MAKE] '.$g->dir );
67             }
68              
69 0 0       0 if( $e =~ m|etc/| ) {
70             # cp etc/haineko.cf-example /path/to/dir/etc/haineko.cf
71 0         0 File::Copy::copy( $e.'-example', $g );
72             } else {
73             # cp libexec/haineko.psgi /path/to/dir/libexec
74 0         0 File::Copy::copy( $e, $g );
75             }
76              
77 0         0 push @$setupfiles, './haineko-setup-files/'.$e;
78 0         0 $self->p( '[COPY] '.$g );
79              
80             } catch {
81             # Failed to copy
82 0     0   0 $self->e( 'Failed to copy file: '.$e );
83             }
84 0         0 }
85              
86             # tar cvf haineko-setup-files.tar.gz
87 0         0 $archiveobj = Archive::Tar->new;
88 0 0       0 chdir( $tempfolder ) || $self->e( 'Cannot change directory: '.$tempfolder );
89 0         0 $archiveobj->add_files( @$setupfiles );
90 0         0 $archiveobj->write( $tararchive, 9 );
91 0         0 $self->p( 'Archive file = '.$tararchive, 1 );
92              
93             # tar archive to BASE64 encoded string
94 0         0 my $filehandle = IO::File->new( $tararchive, 'r' );
95 0         0 my $readbuffer = undef;
96 0         0 my $base64data = q();
97              
98 0         0 while( read $filehandle, $readbuffer, 57 * 60 ) {
99 0         0 $base64data .= MIME::Base64::encode_base64( $readbuffer );
100 0         0 $base64data .= "\n";
101             }
102 0         0 $filehandle->close;
103 0         0 chomp $base64data;
104 0         0 $self->p( 'Base64 encoded data = '.length( $base64data ).' bytes', 1 );
105              
106             # Write BASE64 encoded string to the module
107 0 0       0 chdir( $currentdir ) || $self->e( 'Cannot change directory: '.$currentdir );
108             try {
109 0     0   0 $filehandle = IO::File->new( $modulename, 'w' );
110 0         0 $filehandle->print( 'package Haineko::CLI::Setup::Data;'."\n" );
111 0         0 $filehandle->print( '1;'."\n" );
112 0         0 $filehandle->print( '__DATA__'."\n" );
113 0         0 $filehandle->print( $base64data );
114 0         0 $filehandle->close;
115              
116 0         0 $self->p( 'Update module data: '.$modulename );
117 0         0 $self->p( '[DONE] '.$self->command, 1 );
118              
119             } catch {
120             # Failed to write the module
121 0     0   0 $self->e( 'Failed to write: '.$modulename );
122 0         0 };
123             }
124              
125             sub init {
126 1     1 1 554 my $self = shift;
127 1         5 my $o = __PACKAGE__->options;
128              
129 1 50       6 return undef unless( $self->r & $o->{'exec'} );
130              
131 0         0 my $tempfolder = File::Temp->newdir;
132 0         0 my $tararchive = $tempfolder.'/haineko-setup-files.tar.gz';
133 0         0 my $base64data = [ ];
134 0         0 my $base64text = q();
135 0         0 my $filehandle = undef;
136              
137 0         0 while( my $r = shift @$base64data ) {
138 0         0 chomp $r;
139 0         0 $base64text .= $r;
140             }
141              
142 0         0 $self->p( 'Destination directory = '.$self->{'params'}->{'dest'}, 1 );
143 0 0       0 $self->e( 'Failed to create temporary directory' ) unless $tempfolder;
144 0         0 $self->p( 'Temporary directory = '.$tempfolder, 1 );
145 0 0       0 $self->e( 'Failed to get setup file data' ) unless length $base64text;
146              
147 0         0 $filehandle = IO::File->new( $tararchive, 'w' );
148 0 0       0 $self->e( 'Failed to create the archive file: ' ) unless $filehandle;
149 0         0 $self->p( 'Archive file = '.$tararchive, 1 );
150              
151 0 0       0 if( flock( $filehandle, LOCK_EX ) ) {
152             # Write BASE64 decoded data
153 0         0 my $archiveobj = undef; # (Archive::Tar) Object
154 0         0 my $setupfiles = undef; # (Ref->Array) File list
155 0         0 my $extracted1 = undef; # (String) Extracted directory name
156              
157 0         0 $filehandle->print( MIME::Base64::decode_base64( $base64text ) );
158 0 0       0 $filehandle->close if flock( $filehandle, LOCK_UN );
159              
160 0         0 $archiveobj = Archive::Tar->new;
161 0         0 $archiveobj->read( $tararchive );
162 0         0 $archiveobj->setcwd( $tempfolder );
163 0         0 $archiveobj->extract();
164              
165 0         0 $extracted1 = $tempfolder.'/haineko-setup-files';
166 0 0       0 $self->e( 'Failed to extract the archive' ) unless -d $extracted1;
167 0         0 $self->p( 'Extracted directory = '.$extracted1, 1 );
168              
169 0         0 $setupfiles = __PACKAGE__->list;
170 0         0 for my $e ( @$setupfiles ) {
171 0         0 my $d = $self->{'params'}->{'dest'};
172 0         0 my $f = sprintf( "%s/%s", $extracted1, $e );
173 0         0 my $g = sprintf( "%s/%s", $d, $e );
174 0         0 my $s = Path::Class::Dir->new( File::Basename::dirname $g );
175              
176 0 0 0     0 if( -e $g && ! ( $self->r & $o->{'force'} ) ) {
177 0         0 $self->p( '[SKIP] '.$g, 1 );
178 0         0 next;
179             }
180              
181 0 0       0 if( not -d $s->stringify ) {
182             try {
183             # mkdir -p
184 0     0   0 $s->mkpath;
185             } catch {
186             # Permission denied
187 0     0   0 $self->e( 'Permission denied: '.$s );
188 0         0 };
189 0         0 $self->p( '[MAKE] '.$s->stringify, 1 );
190             }
191              
192 0         0 File::Copy::copy( $f, $g );
193 0 0       0 $self->e( 'Failed to copy: '.$g, 1 ) unless -e $g;
194 0 0       0 $self->p( '[COPY] '.( $self->r & $o->{'force'} ? 'Overwrite: ' : '' ).$g, 1 ) if -e $g;
    0          
195              
196 0 0       0 if( $g =~ m|/authinfo| ) {
197 0         0 chmod( 0600, $g );
198 0         0 $self->p( '[PERM] 0600 '.$g, 1 );
199             }
200              
201 0 0       0 next unless $g =~ m|/bin/|;
202 0         0 chmod( 0755, $g );
203 0         0 $self->p( '[PERM] 0755 '.$g, 1 );
204             }
205 0         0 $self->p( '[DONE] '.$self->command, 1 );
206              
207             } else {
208 0         0 $self->e( 'Failed to write data to '.$tararchive );
209             }
210             }
211              
212             sub parseoptions {
213 1     1 1 3 my $self = shift;
214 1         5 my $opts = __PACKAGE__->options;
215              
216 1         3 my $r = 0; # Run mode value
217 1         3 my $p = {}; # Parsed options
218              
219 1     1   1492 use Getopt::Long qw/:config posix_default no_ignore_case bundling auto_help/;
  1         34990  
  1         7  
220 1         8 Getopt::Long::GetOptions( $p,
221             'dest=s', # Destination directory
222             'force', # Force overwrite
223             'help', # --help
224             'verbose|v+', # Verbose
225             );
226              
227 1 50       323 if( $p->{'help'} ) {
228             # --help
229 0         0 require Haineko::CLI::Help;
230 0         0 my $o = Haineko::CLI::Help->new( 'command' => [ caller ]->[1] );
231 0         0 $o->add( __PACKAGE__->help('s'), 'subcommand' );
232 0         0 $o->add( __PACKAGE__->help('o'), 'option' );
233 0         0 $o->add( __PACKAGE__->help('e'), 'example' );
234 0         0 $o->mesg;
235 0         0 exit(0);
236             }
237              
238 1 50       5 $r |= $opts->{'force'} if $p->{'force'}; # Overwrite by force
239              
240 1         9 $self->v( $p->{'verbose'} );
241 1         5 $self->v( $self->v + 1 );
242 1   50     11 $self->{'params'}->{'dest'} = $p->{'dest'} // '.'; # Destination directory
243              
244 1         3 $r |= $opts->{'exec'};
245 1         4 $self->r( $r );
246 1         6 return $r;
247             }
248              
249             sub help {
250 4     4 1 1512 my $class = shift;
251 4   100     21 my $argvs = shift || q();
252              
253 4         17 my $commoption = [
254             '--dest ' => 'Destination directory for setup files.',
255             '--force' => 'Overwrite distribution files by force.',
256             '-v, --verbose' => 'Verbose mode.',
257             '--help' => 'This screen',
258             ];
259 4         10 my $subcommand = [
260             'setup' => 'Setup files for Haineko.',
261             'make-setup-files' => 'For author: Update lib/Haineko/CLI/Setup/Data.pm',
262             ];
263 4         7 my $forexample = [
264             'hainekoctl setup # Copy files to current directory',
265             'hainekoctl setup --dest /usr/local/haineko/etc'
266             ];
267              
268 4 100 66     33 return $commoption if $argvs eq 'o' || $argvs eq 'option';
269 3 100 66     18 return $subcommand if $argvs eq 's' || $argvs eq 'subcommand';
270 2 100 66     13 return $forexample if $argvs eq 'e' || $argvs eq 'example';
271 1         4 return undef;
272             }
273              
274             1;
275             __END__