File Coverage

lib/Haineko/CLI/Daemon.pm
Criterion Covered Total %
statement 10 14 71.4
branch n/a
condition n/a
subroutine 4 6 66.6
pod n/a
total 14 20 70.0


line stmt bran cond sub pod time code
1             package Haineko::CLI::Daemon;
2 1     1   5337 use parent 'Haineko::CLI';
  1         724  
  1         9  
3 1     1   48 use strict;
  1         2  
  1         41  
4 1     1   8 use warnings;
  1         1  
  1         512  
5              
6             sub options {
7             return {
8 0     0     'exec' => ( 1 << 0 ),
9             'test' => ( 1 << 1 ),
10             'auth' => ( 1 << 2 ),
11             };
12             }
13              
14             sub default {
15             return {
16 0     0     'env' => 'production',
17             'app' => '',
18             'root' => '',
19             'host' => '127.0.0.1',
20             'port' => 2794,
21             'config' => '',
22             'server' => 'Standalone',
23             'logging' => { 'disabled' => 1, 'facility' => 'user', 'file' => '' },
24             'workers' => 2,
25             'maxreqs' => 100,
26             'interval'=> 2,
27             };
28             }
29              
30             sub run {
31             my $self = shift;
32              
33             my $o = __PACKAGE__->options;
34             my $r = $self->r;
35             my $p = $self->{'params'};
36              
37             return 0 unless $r & $o->{'exec'};
38              
39             my $runnerprog = undef;
40             my $watchingon = [];
41             my $plackuparg = [];
42             my $commandarg = q();
43              
44             $ENV{'PLACKENV'} = $p->{'env'};
45             $ENV{'HAINEKO_ROOT'} = $p->{'root'};
46             $ENV{'HAINEKO_CONF'} = $p->{'config'};
47             $ENV{'HAINEKO_AUTH'} = $p->{'root'}.'/etc/password';
48              
49             push @$watchingon, './lib' if -d './lib';
50             push @$watchingon, './etc' if -d './etc';
51             push @$watchingon, $p->{'root'}.'/etc';
52             push @$watchingon, $p->{'root'}.'/lib';
53             push @$plackuparg, '-R', join( ',', @$watchingon );
54              
55             push @$plackuparg, '-a', $p->{'app'};
56             push @$plackuparg, '-o', $p->{'host'};
57             push @$plackuparg, '-p', $p->{'port'};
58             push @$plackuparg, '-L', 'Restarter';
59             push @$plackuparg, '-s', $p->{'server'};
60              
61              
62             if( $p->{'server'} eq 'Starlet' ) {
63             # −−max−workers=#
64             # number of worker processes (default: 10)
65             push @$plackuparg, '--max-workers', $p->{'workers'};
66              
67             # −−max−reqs−per−child=#
68             # max. number of requests to be handled before a worker process exits
69             # (default: 100)
70             push @$plackuparg, '--max-reqs-per-child', $p->{'maxreqs'};
71              
72             } elsif( $p->{'server'} eq 'Starman' ) {
73             # −−workers
74             # Specifies the number of worker pool. Defaults to 5.
75             push @$plackuparg, '--workers', $p->{'workers'};
76              
77             # −−max−requests
78             # Number of the requests to process per one worker process. Defaults
79             # to 1000.
80             push @$plackuparg, '--max-requests', $p->{'maxreqs'};
81             }
82              
83             if( length $self->{'logging'}->{'file'} ) {
84             # --access-log /path/to/logfile
85             push @$plackuparg, '--access-log', $self->{'logging'}->{'file'};
86             }
87              
88              
89             if( $r & $o->{'test'} ) {
90             # Development mode
91             require Plack::Runner;
92             $runnerprog = Plack::Runner->new;
93             $ENV{'HAINEKO_DEBUG'} = 1;
94              
95             if( $r & $o->{'auth'} ) {
96             # Require Basic-Authentication when connected to Haineko server
97             if( -f $p->{'root'}.'/etc/password-debug' && -r _ && -e _ ) {
98             # Use etc/password-debug if it exists
99             $ENV{'HAINEKO_AUTH'} = $p->{'root'}.'/etc/password-debug';
100             $self->p( 'Require Basic-Authentication: '.$ENV{'HAINEKO_AUTH'}, 1 );
101             }
102             }
103              
104             $self->makepf;
105             $runnerprog->parse_options( @$plackuparg );
106             $runnerprog->run;
107             $self->p( 'Start Haineko server', 0 );
108              
109             } else {
110             # Production mode
111 1     1   1962 use Server::Starter qw(start_server restart_server);
  0            
  0            
112              
113             if( $r & $o->{'auth'} ) {
114             # Require Basic-Authentication when connected to Haineko server
115             if( -f $p->{'root'}.'/etc/password' && -r _ && -e _ ) {
116             # Use etc/password-debug if it exists
117             $ENV{'HAINEKO_AUTH'} = $p->{'root'}.'/etc/password';
118             $self->p( 'Require Basic-Authentication: '.$ENV{'HAINEKO_AUTH'}, 1 );
119             }
120             }
121              
122             # Status file is saved in the same directory of pid file.
123             my $s = $self->{'pidfile'}; $s =~ s|[.]pid|.status|;
124              
125             unshift @$plackuparg, __PACKAGE__->which('plackup');
126             push @$plackuparg, '--daemonize';
127              
128             $commandarg .= 'nohup ';
129             $commandarg .= __PACKAGE__->which('start_server');
130             $commandarg .= ' --port='.$p->{'port'};
131             $commandarg .= ' --interval='.$p->{'interval'};
132             $commandarg .= ' --pid-file='.$self->{'pidfile'};
133             $commandarg .= ' --status-file='.$s;
134             $commandarg .= ' -- ';
135              
136             if( $self->makerf( $plackuparg ) ) {
137             # command line for starting plackup is saved in run/haineko.sh, and
138             # the file is the argument of start_server.
139             $commandarg .= $self->{'runfile'};
140              
141             } else {
142             # command line for starting plackup is the argument of start_server.
143             $commandarg .= join( ' ', @$plackuparg );
144             }
145             $commandarg .= ' > /dev/null &';
146              
147             $self->p( 'Start Haineko server', 0 );
148             exec $commandarg;
149             }
150             }
151              
152             sub ctrl {
153             my $self = shift;
154             my $argv = shift || return undef;
155             my $sigs = {
156             'stop' => 'TERM',
157             'reload' => 'USR1',
158             'restart' => 'HUP',
159             };
160              
161             return undef unless $argv =~ m/\A(?:start|stop|reload|restart)\z/;
162             if( $argv eq 'start' ) {
163             # start haineko server
164             $self->run;
165              
166             } else {
167             # stop, reload, and restart haineko server
168             my $p = $self->readpf;
169             my $s = 0;
170              
171             $self->e( sprintf( "Cannot read %s", $self->pidfile ) ) unless $p;
172             $s = kill( $sigs->{ $argv }, $p );
173              
174             if( $argv eq 'stop' ) {
175             # Sleep for a few seconds until the process exits
176             sleep $self->{'params'}->{'interval'};
177              
178             if( kill( 0, $p ) ) {
179             # If the process is still running, send 'KILL' signal to the
180             # process
181             kill( 'KILL', $p );
182             sleep $self->{'params'}->{'interval'};
183             }
184              
185             $self->{'runfile'} = $self->{'pidfile'};
186             $self->{'runfile'} =~ s|[.]pid|.sh|;
187             $self->removepf;
188             $self->removerf;
189             }
190             $self->p( ucfirst $argv.' Haineko server', 0 );
191             return $s;
192             }
193             }
194              
195             sub parseoptions {
196             my $self = shift;
197             my $dirs = [ '.', '/usr/local/haineko', '/usr/local' ];
198             my $opts = __PACKAGE__->options;
199             my $defs = __PACKAGE__->default;
200             my $conf = {}; %$conf = %$defs;
201              
202             my $r = 0; # Run mode value
203             my $p = {}; # Parsed options
204             my $q = undef; # Path::Class::File
205              
206             use Getopt::Long qw/:config posix_default no_ignore_case bundling auto_help/;
207             Getopt::Long::GetOptions( $p,
208             'app|a=s', # Path to psgi file
209             'auth|A', # Require basic-authenticaion
210             'conf|C=s', # Configuration file
211             'devel|d', # Developement mode
212             'debug', # same as --devel
213             'help', # --help
214             'host|h=s', # Hostname
215             'log|l=s', # Access log
216             'port|p=i', # Port
217             'server|s=s', # Server, -s option of plackup
218             'workers|w=i', # --max-workers of plackup
219             'maxreqs|x=i', # --max-requests
220             'verbose|v+', # Verbose
221             );
222              
223             if( $p->{'help'} ) {
224             # --help
225             require Haineko::CLI::Help;
226             my $o = Haineko::CLI::Help->new( 'command' => [ caller ]->[1] );
227             $o->add( __PACKAGE__->help('s'), 'subcommand' );
228             $o->add( __PACKAGE__->help('o'), 'option' );
229             $o->add( __PACKAGE__->help('e'), 'example' );
230             $o->mesg;
231             exit(0);
232             }
233              
234             if( defined $p->{'devel'} || defined $p->{'debug'} ) {
235             # Turn on the development mode
236             $r |= $opts->{'test'};
237             $conf->{'env'} = 'development';
238             }
239              
240             # Require Basic-Authentication
241             $r |= $opts->{'auth'} if defined $p->{'auth'};
242              
243             if( $p->{'conf'} ) {
244             # Load configuration file specified with -C or --conf option
245             if( -f $p->{'conf'} && -r _ && -s _ ) {
246             $conf->{'config'} = $p->{'conf'};
247              
248             } else {
249             $self->e( sprintf( "Config file: %s not found", $p->{'conf'} ) ) unless -f $p->{'conf'};
250             $self->e( sprintf( "Config file: %s is empty", $p->{'conf'} ) ) unless -s $p->{'conf'};
251             $self->e( sprintf( "Config file: cannot read %s", $p->{'conf'} ) ) unless -r $p->{'conf'};
252             }
253              
254             } else {
255             # No configuration file specified at -C option
256             for my $g ( @$dirs ) {
257             # Find haineko.cf
258             my $f = sprintf( "%s/etc/haineko.cf", $g );
259             my $v = $r & $opts->{'test'} ? $f.'-debug' : q();
260              
261             if( $v && -f $v && -s _ && -r _ ) {
262             # etc/haineko.cf-debug exists;
263             $conf->{'config'} = $v;
264             last;
265             }
266             next unless -f $f;
267             next unless -s $f;
268             next unless -r $f;
269              
270             $conf->{'config'} = $f;
271             last;
272             }
273             }
274              
275             if( $conf->{'config'} ) {
276             $q = Path::Class::File->new( $conf->{'config'} )->dir;
277             $conf->{'root'} = $q->resolve->absolute->parent;
278              
279             } else {
280             $conf->{'config'} = '/dev/null';
281             $q = Path::Class::Dir->new( './' );
282             $conf->{'root'} = $q->resolve->absolute;
283             }
284              
285             if( $p->{'app'} ) {
286             if( -f $p->{'app'} && -s _ && -r _ ) {
287             # Set the path to haineko.psgi
288             $conf->{'app'} = $p->{'app'};
289              
290             } else {
291             # haineko.psgi not found
292             $self->e( sprintf( "PSGI file: %s not found", $p->{'app'} ) ) unless -f $p->{'app'};
293             $self->e( sprintf( "PSGI file: %s is empty", $p->{'app'} ) ) unless -s $p->{'app'};
294             $self->e( sprintf( "PSGI file: cannot read %s", $p->{'app'} ) ) unless -r $p->{'app'};
295             }
296              
297             } else {
298             for my $g ( @$dirs ) {
299             # Find haineko.psgi
300             my $f = sprintf( "%s/libexec/haineko.psgi", $g );
301             next unless -f $f;
302             next unless -s $f;
303             next unless -r $f;
304              
305             $conf->{'app'} = $f;
306             last;
307             }
308             }
309              
310             for my $e ( 'host', 'port' ) {
311             # Host, Port and PSGI file
312             next unless defined $p->{ $e };
313             $conf->{ $e } = $p->{ $e };
314             }
315             for my $e ( 'server', 'workers' ) {
316             # Override the value with the value in argument
317             next unless $p->{ $e };
318             $conf->{ $e } = $p->{ $e };
319             }
320              
321             $self->v( $p->{'verbose'} );
322             $self->p( sprintf( "Run mode = %d", $r ), 1 );
323             $self->p( sprintf( "Debug level = %d", $self->v ), 1 );
324             $self->p( sprintf( "Hostname = %s", $conf->{'host'} ), 1 );
325             $self->p( sprintf( "Port = %d", $conf->{'port'} ), 1 );
326             $self->p( sprintf( "Server = %s", $conf->{'server'} ), 1 );
327             $self->p( sprintf( "PSGI application file = %s", $conf->{'app'} ), 1 );
328             $self->p( sprintf( "PLACKENV value = %s", $conf->{'env'} ), 1 );
329             $self->p( sprintf( "Configuration file = %s", $conf->{'config'} ), 1 );
330              
331             if( $p->{'log'} ) {
332              
333             $self->{'logging'}->{'disabled'} = 0;
334             $self->{'logging'}->{'file'} = $p->{'log'};
335             $self->p( sprintf( "Access log file = %s", $p->{'log'} ) );
336              
337             } else {
338              
339             $self->{'logging'} = $conf->{'logging'} // $defs->{'logging'};
340             if( not $self->{'logging'}->{'disabled'} ) {
341             # syslog
342             $self->p( sprintf( "Syslog disabled = %d", $self->{'logging'}->{'disabled'} ), 2 );
343             $self->p( sprintf( "Syslog facility = %s", $self->{'logging'}->{'facility'} ), 2 );
344             }
345             }
346              
347             $r |= $opts->{'exec'};
348             $self->r( $r );
349             $self->{'params'} = $conf;
350             return $r;
351             }
352              
353             sub help {
354             my $class = shift;
355             my $argvs = shift || q();
356              
357             my $d = __PACKAGE__->default;
358             my $commoption = [
359             '-A, --auth' => 'Require Basic Authentication.',
360             '-a, --app ' => 'Path to a psgi file.',
361             '-C, --conf ' => 'Path to a configuration file.',
362             '-d, --devel,--debug' => 'Run on developement mode.',
363             '-h, --host ' => 'Binds to a TCP interface. default: '.$d->{'host'},
364             '-l, --log ' => 'Access log.',
365             '-p, --port ' => 'Binds to a TCP port. default: '.$d->{'port'},
366             '-s, --server '=> 'Server implementation to run on for plackup -s. default: '.$d->{'server'},
367             '-w, --workers ' => 'The number of max workers for Handler(-s option). default: '.$d->{'workers'},
368             '-x, --maxreqs ' => 'The number of max requests per child. default: '.$d->{'maxreqs'},
369             '-v, --verbose' => 'Verbose mode.',
370             '--help' => 'This screen',
371             ];
372             my $subcommand = [
373             'start' => 'Start haineko server',
374             'reload' => 'Send "USR1" signal to the server',
375             'restart' => 'Restart the server, send "HUP" signal',
376             'stop' => 'Stop the server, send "TERM" signal',
377             'status' => 'Show the process id of running haineko server',
378             ];
379             my $forexample = [
380             'hainekoctl start -s Starlet -w 4 -x 1000',
381             'hainekoctl start -d -h 127.0.0.1 -p 2222 -C /tmp/neko.cf',
382             ];
383              
384             return $commoption if $argvs eq 'o' || $argvs eq 'option';
385             return $subcommand if $argvs eq 's' || $argvs eq 'subcommand';
386             return $forexample if $argvs eq 'e' || $argvs eq 'example';
387             return undef;
388             }
389              
390             1;
391             __END__