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__ |