File Coverage

blib/lib/Pepper/Commander.pm
Criterion Covered Total %
statement 32 212 15.0
branch 0 80 0.0
condition 0 32 0.0
subroutine 11 22 50.0
pod 0 11 0.0
total 43 357 12.0


line stmt bran cond sub pod time code
1             package Pepper::Commander;
2              
3 1     1   2479 use 5.022001;
  1         4  
4 1     1   5 use strict;
  1         2  
  1         19  
5 1     1   5 use warnings;
  1         2  
  1         54  
6              
7             our $VERSION = "1.5";
8              
9             # for accepting options
10 1     1   1570 use IO::Prompter;
  1         36938  
  1         6  
11              
12             # for getting default hostname/domain name
13 1     1   623 use Net::Domain qw( hostfqdn domainname );
  1         8900  
  1         69  
14              
15             # for doing everything else
16 1     1   8 use Pepper;
  1         2  
  1         20  
17 1     1   4 use Pepper::DB;
  1         2  
  1         17  
18 1     1   492 use Pepper::Templates;
  1         3  
  1         32  
19              
20             # for test-endpoint
21 1     1   475 use Plack::Test;
  1         550  
  1         64  
22 1     1   1121 use Plack::Util;
  1         2969  
  1         30  
23 1     1   567 use HTTP::Request::Common;
  1         19764  
  1         2782  
24              
25             # create myself and try to grab arguments
26             sub new {
27 0     0 0   my ($class) = @_;
28              
29             # set up the object with all the options data
30             my $self = bless {
31 0           'pepper_directory' => $ENV{HOME}.'/pepper',
32             'pepper' => Pepper->new(
33             'skip_db' => 1,
34             'skip_config' => 1,
35             ),
36             }, $class;
37              
38 0           return $self;
39             }
40              
41             # dispatch based on $ARGV[0]
42             sub run {
43 0     0 0   my ($self,@args) = @_;
44            
45 0           my $dispatch = {
46             'help' => 'help_screen',
47             'setup' => 'setup_and_configure',
48             'config' => 'setup_and_configure',
49             'set-endpoint' => 'set_endpoint',
50             'list-endpoints' => 'list_endpoints',
51             'delete-endpoint' => 'delete_endpoint',
52             'test-db' => 'test_db',
53             'test-endpoint' => 'test_endpoint',
54             'start' => 'plack_controller',
55             'stop' => 'plack_controller',
56             'restart' => 'plack_controller',
57             };
58            
59             # have to be one of these
60 0 0 0       if (!$args[0] || !$$dispatch{$args[0]}) {
    0 0        
61              
62 0           die "Usage: pepper help|setup|set-endpoint|delete-endpoint|list-endpoints|test-db|test-endpoint|start|stop|restart\n";
63            
64             # can not do anything without a config file
65             } elsif ($args[0] ne 'setup' && !(-e $self->{pepper}->{utils}->{config_file})) {
66 0           die "You must run 'pepper setup' to create a config file.\n";
67            
68             # otherwise, run it
69             } else {
70 0           my $method = $$dispatch{$args[0]};
71 0           $self->$method(@args);
72            
73             }
74            
75             }
76              
77             # print documentation on how to use this
78             sub help_screen {
79 0     0 0   my ($self,@args) = @_;
80              
81 0           print qq{
82              
83             pepper: Utility command to configure and control the Pepper environment.
84              
85             # sudo pepper setup
86              
87             This is the configuration mode. The Pepper workspace will be created as a 'pepper'
88             directory within your home directory, aka $ENV{HOME}/pepper, unless it already exists.
89             You will be prompted for the configuration options, and your configuration file will
90             be created or overwritten.
91              
92             # pepper test-db
93              
94             This will perform a basic connection / query test on the database config you provided
95             via 'pepper setup'.
96              
97             # pepper set-endpoint [URI] [PerlModule]
98              
99             This creates an endpoint mapping in Pepper to tell Plack how to dispatch incoming
100             requests. The first argument is a URI and the second is a target Perl module for
101             handing GET/POST requests to the URI. If these two arguments are not given, you
102             will be prompted for the information.
103              
104             If the Perl module does not exist under $ENV{HOME}/pepper/lib, an initial version will be created.
105              
106             # pepper list-endpoints
107              
108             This will output your configured endpoint mappings.
109              
110             # pepper test-endpoint [URI]
111              
112             This will run Plack::Test against the URI's endpoint to see that it will return 200 OK.
113             This is not a test of functionality, just a test that the endpoint executes and returns 200.
114              
115             # pepper delete-endpoint [URI]
116              
117             Removes an endpoint mapping from Pepper. The Perl module will not be deleted.
118              
119             # pepper start [#Workers] [dev-reload]
120              
121             Attempts to start the Plack service. Provide an integer for the #Workers to spcify the
122             maximum number of Plack processes to run. The default is 10.
123              
124             If you indicate a number of workers plus 'dev-reload' as the third argument, Plack
125             will be started with the auto-reload option to auto-detect changes to your code.
126             If that is not provided, you will need to issue 'pepper restart' to put your code
127             changes into effect. Enabling dev-reload will slow down Plack significantly, so it
128             is only appropriate for development environments.
129              
130             # pepper restart
131              
132             Restarts the Plack service and put your code changes into effect.
133              
134             };
135              
136             }
137              
138             # test that the database connection works; hard to do this in module install
139             sub test_db {
140 0     0 0   my ($self,$mode) = @_;
141            
142 0   0       $mode ||= 'print';
143            
144 0           my $utils = $self->{pepper}->{utils};
145 0           $utils->read_system_configuration();
146            
147 0           my $db;
148 0           eval {
149             $db = Pepper::DB->new({
150             'config' => $utils->{config},
151 0           'utils' => $utils,
152             });
153             };
154            
155 0           my $result_message = '';
156 0           my $current_timestamp = '';
157 0 0         if ($@) {
158 0           $result_message = "\n\nCOULD NOT CONNECT TO THE DATABASE.\nERROR MESSAGE: $@";
159 0           $result_message .= "\nPlease confirm config and re-run 'sudo pepper setup' as needed.\n\n";
160            
161             # connected / test querying
162             } else {
163 0           ($current_timestamp) = $db->quick_select('select current_timestamp');
164            
165             }
166            
167             # if the query succeeded...
168 0 0         if ($current_timestamp =~ /^\d{4}\-\d{2}\-\d{2}\s/) {
    0          
169 0           $result_message = "\nYour database connection appears to be working.\n\n";
170            
171             # if it did not and there is no connection error...
172             } elsif (!$result_message) {
173 0           $result_message = "\n\nCOULD NOT QUERY THE DATABASE.\nERROR MESSAGE: $@".
174             "\nPlease confirm config and re-run 'sudo pepper setup' as needed.\n\n";
175             }
176              
177             # if we are in setup mode, we will exit here; otherwise, we just print
178 0           print $result_message;
179 0 0 0       if ($mode eq 'setup' && $result_message =~ /ERROR/) {
180 0           exit;
181             }
182              
183             }
184              
185             # create directory structure, build configs, create examples
186             sub setup_and_configure {
187 0     0 0   my ($self,@args) = @_;
188              
189 0           my ($config_options_map, $config, $subdir_full, $subdir, $map_set, $key);
190              
191 0 0         if (! (-d $self->{pepper_directory} ) ) {
192 0           mkdir ($self->{pepper_directory});
193             }
194 0           foreach $subdir ('lib','config','psgi','log','template','template/system') {
195 0           $subdir_full = $self->{pepper_directory}.'/'.$subdir;
196 0 0         mkdir ($subdir_full) if !(-d $subdir_full);
197             }
198            
199             # sanity
200 0           my $utils = $self->{pepper}->{utils};
201            
202 0           $config_options_map = [
203             ['development_server',
204             qq{
205             Is this a development server?
206             If you select 'Y', errors will be piped to the screen and logged.
207             Select 'N' for production servers, where errors will be logged but not shown to the user.
208             (Y or N)},'Y'],
209             ['use_database',qq{
210             Auto-Connect to a MySQL/MariaDB database server?
211             This will make the database/SQL methods available via the \$pepper object.
212             (Y or N)},'Y'],
213             ['database_server', "\n".'Hostname or IP Address for your MySQL/MariaDB server (required)'],
214             ['database_username', "\n".'Username to connect to your MySQL/MariaDB server (required)'],
215             ['database_password', "\n".'Password to connect to your MySQL/MariaDB server (required)'],
216             ['connect_to_database', qq{
217             Default database for the MySQL/MariaDB connection.},'information_schema'],
218             ['url_mappings_database',
219             qq{
220             Database to store URL/endpoint mappings.
221             A 'pepper_endpoints' table will be created and maintained via 'pepper set-endpoint'.
222             This is a faster option for handling requests, but you may leave blank for JSON config file.}],
223             ['default_endpoint_module',
224             qq{
225             Default endpoint-handler Perl module.
226             This will be used for URI's that have not been configured via 'pepper set-endpoint'.
227             Exapme: PepperApps::NiceEndPoint. Built under $self->{pepper_directory}/lib.
228             Leave blank for example module.}],
229             ];
230            
231             # does a configuration already exist?
232 0 0         if (-e $utils->{config_file}) {
233 0           $utils->read_system_configuration();
234 0           foreach $map_set (@$config_options_map) {
235 0           $key = $$map_set[0];
236 0 0         $$map_set[2] = $utils->{config}{$key} if $utils->{config}{$key};
237             }
238             }
239            
240             # shared method below
241 0           $config = $self->prompt_user($config_options_map);
242            
243             # calculate the endpoint storage
244 0 0         if ($$config{url_mappings_database}) {
245 0           $$config{url_mappings_table} = $$config{url_mappings_database}.'.pepper_endpoints';
246             } else {
247 0           $$config{url_mappings_file} = $self->{pepper_directory}.'/config/pepper_endpoints.json';
248             }
249              
250             # default endpoint handler
251 0   0       $$config{default_endpoint_module} ||= 'PepperApps::PepperExample';
252            
253             # now write the config file
254 0           $utils->write_system_configuration($config);
255            
256             # if they want to connect to a database, let's test that now
257 0 0         if ($$config{use_database} eq 'Y') {
258 0           $self->test_db('setup');
259             }
260              
261             # install some needed templates
262 0           my $template_files = {
263             'endpoint_handler.tt' => 'endpoint_handler',
264             'html_example.tt' => 'html_example_endpoint',
265             'pepper.psgi' => 'psgi_script',
266             'pepper_apache.conf' => 'apache_config',
267             'pepper.service' => 'systemd_config',
268             'example_perl_script.pl' => 'sample_script',
269             };
270 0           my $pepper_templates = Pepper::Templates->new();
271              
272 0           foreach my $t_file (keys %$template_files) {
273 0           my $dest_dir = 'template/system'; # everything by the PSGI script goes in 'template'
274 0 0         $dest_dir = 'psgi' if $t_file eq 'pepper.psgi';
275 0           my $dest_file = $self->{pepper_directory}.'/'.$dest_dir.'/'.$t_file;
276              
277             # skip if already in place
278 0 0         next if -e $dest_file;
279            
280             # save the new template file
281 0           my $template_method = $$template_files{$t_file};
282 0           my $contents = $pepper_templates->$template_method();
283            
284             # set the username for the SystemD service
285 0           $contents =~ s/User=root/User=$ENV{USER}/;
286            
287             # target directories for PSGI logs
288 0           $contents =~ s/PEPPER_DIRECTORY/$self->{pepper_directory}/g;
289            
290             # now save it out
291 0           $utils->filer($dest_file,'write',$contents);
292             }
293              
294             # set the default example endpoint; hopefully the example module
295 0           $self->set_endpoint('default','default',$$config{default_endpoint_module});
296              
297             # if the HTML endpoint example isn't already there, add it in
298 0           my $html_example_handler = $self->{pepper_directory}.'/lib/PepperApps/HTMLExample.pm';
299 0 0         if (!(-e "$html_example_handler")) {
300 0           mkdir( $self->{pepper_directory}.'/lib/PepperApps');
301 0           $self->set_endpoint('/pepper/html_example','/pepper/html_example','PepperApps::HTMLExample');
302             # make it second, so nothing will be said on first go-round
303 0           my $html_example_code = $pepper_templates->html_example_endpoint('perl');
304 0           $utils->filer($html_example_handler,'write',$html_example_code);
305             }
306              
307 0           print "\nConfiguration complete and workspace ready under $self->{pepper_directory}\n";
308              
309             }
310              
311             # method to add an endpoint mapping to the system
312             sub set_endpoint {
313 0     0 0   my ($self,@args) = @_;
314            
315 0           my ($endpoint_data, $endpoint_prompts, $extra_text, $module_file);
316            
317 0           my $utils = $self->{pepper}->{utils}; # sanity
318            
319             # we need the configuration for this
320 0           $utils->read_system_configuration();
321              
322             # create a DB object if saving to a table
323 0 0         if ($utils->{config}{url_mappings_table}) {
324             $utils->{db} = Pepper::DB->new({
325             'config' => $utils->{config},
326 0           'utils' => $utils,
327             });
328             }
329            
330             $endpoint_prompts = [
331 0           ['endpoint_uri','URI for endpoint, such as /hello/world (required)'],
332             ['endpoint_handler', 'Module name for endpoint, such as PepperApps::HelloWorld (required)'],
333             ];
334              
335             # if they passed in two args, we can use those for the endpoints
336 0 0 0       if ($args[1] && $args[2]) {
337            
338 0           $endpoint_data = {
339             'endpoint_uri' => $args[1],
340             'endpoint_handler' => $args[2],
341             };
342            
343             # otherwise, prompt them for the information
344             } else {
345             # shared method below
346 0           $endpoint_data = $self->prompt_user($endpoint_prompts);
347             }
348            
349             # commit the change
350 0           $utils->set_endpoint_mapping( $$endpoint_data{endpoint_uri}, $$endpoint_data{endpoint_handler} );
351            
352             # create the module, if it does not exist
353 0           my (@module_path, $directory_path, $part);
354 0           (@module_path) = split /\:\:/, $$endpoint_data{endpoint_handler};
355 0 0         if ($module_path[1]) {
356 0           $directory_path = $ENV{HOME}.'/pepper/lib';
357 0           foreach $part (@module_path) {
358 0 0         if ($part ne $module_path[-1]) {
359 0           $directory_path .= '/'.$part;
360 0 0         if (!(-d $directory_path)) {
361 0           mkdir($directory_path);
362             }
363             }
364             }
365             }
366            
367             # for the directory in the endpoint
368 0           $$endpoint_data{pepper_directory} = $self->{pepper_directory};
369            
370 0           ($module_file = $$endpoint_data{endpoint_handler}) =~ s/\:\:/\//g;
371 0           $module_file = $self->{pepper_directory}.'/lib/'.$module_file.'.pm';
372 0 0         if (!(-e $module_file)) { # start the handler
373 0           $utils->template_process({
374             'template_file' => 'system/endpoint_handler.tt',
375             'template_vars' => $endpoint_data,
376             'save_file' => $module_file
377             });
378 0           $extra_text = "\n".$module_file." was created. Please edit to taste\n";
379            
380             } else {
381 0           $extra_text = "\n".$module_file." already exists and was left unchanged.\n";
382             }
383            
384             # all done
385 0           print "\nEndpoint configured for $$endpoint_data{endpoint_uri}\n".$extra_text;
386            
387             }
388              
389             # method to remove an endpoint mapping from the system
390             sub delete_endpoint {
391 0     0 0   my ($self,@args) = @_;
392              
393 0           my $endpoint_prompts = [
394             ['endpoint_uri','URI for endpoint to delete, such as /hello/world (required)'],
395             ];
396            
397 0           my $endpoint_data;
398              
399             # if they passed in two args, we can use those for the endpoints
400 0 0         if ($args[1]) {
401            
402 0           $endpoint_data = {
403             'endpoint_uri' => $args[1],
404             };
405            
406             # otherwise, prompt them for the information via shared method below
407             } else {
408 0           $endpoint_data = $self->prompt_user($endpoint_prompts);
409             }
410              
411             # we need the configuration for this
412 0           my $utils = $self->{pepper}->{utils}; # sanity
413 0           $utils->read_system_configuration();
414              
415             # create a DB object if saving endpoints in a table
416 0 0         if ($utils->{config}{url_mappings_table}) {
417             $utils->{db} = Pepper::DB->new({
418             'config' => $utils->{config},
419 0           'utils' => $utils,
420             });
421             }
422            
423             # now delete the endpoint
424 0           $utils->delete_endpoint_mapping( $$endpoint_data{endpoint_uri} );
425              
426             # all done
427 0           print "\nDeleted endpoint for $$endpoint_data{endpoint_uri}\n";
428            
429             }
430              
431              
432             # method to list all of the existing endpoints
433             sub list_endpoints {
434 0     0 0   my ($self) = @_;
435              
436 0           my $utils = $self->{pepper}->{utils}; # sanity
437            
438             # we need the configuration for this
439 0           $utils->read_system_configuration();
440            
441 0           my $url_mappings = {};
442              
443             # create a DB object if saving to a table
444 0 0         if ($utils->{config}{url_mappings_table}) {
    0          
445             $utils->{db} = Pepper::DB->new({
446             'config' => $utils->{config},
447 0           'utils' => $utils,
448             });
449            
450 0           my $url_mappings_array = $utils->{db}->do_sql(qq{
451             select endpoint_uri,handler_module from $utils->{config}{url_mappings_table}
452             });
453 0           foreach my $map (@$url_mappings_array) {
454 0           $$url_mappings{$$map[0]} = $$map[1];
455             }
456            
457             # or maybe a JSON file
458             } elsif ($utils->{config}{url_mappings_file}) {
459            
460 0           $url_mappings = $utils->read_json_file( $utils->{config}{url_mappings_file} );
461            
462             }
463              
464             # get a sorted list of URLs
465 0           my @urls = sort keys %$url_mappings;
466              
467             # no urls?
468 0 0         if (!$urls[0]) {
469 0           print "\nNo endpoints are configured. Please use 'pepper set-endpoint'.\n\n";
470 0           return;
471             }
472            
473             # otherwise, print them out
474 0           print "\nCurrent URL-to-code mappings:\n";
475 0           foreach my $url (@urls) {
476 0           print "\n$url --> $$url_mappings{$url}\n";
477             }
478 0           print "\n";
479              
480 0           return;
481             }
482              
483             # method to test an endpoint
484             sub test_endpoint {
485 0     0 0   my ($self,@args) = @_;
486            
487             # due to how the command works, it should be in the second one
488 0           my $endpoint_data = {};
489 0           my $endpoint_uri;
490 0 0         if (!$args[1]) { # prompt the user for input
491            
492 0           $endpoint_data = $self->prompt_user([
493             ['endpoint_uri','URI for endpoint to test, such as /hello/world (required)'],
494             ]);
495            
496 0           $endpoint_uri = $$endpoint_data{endpoint_uri};
497            
498             } else { # otherwise, they provided it
499 0           $endpoint_uri = $args[1];
500              
501             }
502              
503             # return the test
504 0           print "\nTesting $endpoint_uri...\n";
505 0           my $app = Plack::Util::load_psgi $self->{pepper_directory}.'/psgi/pepper.psgi';
506 0           my $test = Plack::Test->create($app);
507 0           my $res = $test->request(GET $endpoint_uri);
508            
509             # provide the results
510 0 0         if ($res->status_line eq '200 OK' ) {
511 0           print "Success: Endpoint returns 200 OK\n";
512             } else {
513 0           print "Error: Endpoint returns 500 Internal Server Error. Check fatal log under $self->{pepper_directory}/log\n";
514             }
515              
516             }
517              
518             # method to intercept prompts
519             sub prompt_user {
520 0     0 0   my ($self,$prompts_map) = @_;
521            
522 0           my ($prompt_key, $prompt_set, $results, $the_prompt);
523            
524 0           $$results{use_database} = 'Y'; # default for below
525            
526 0           foreach $prompt_set (@$prompts_map) {
527 0           $prompt_key = $$prompt_set[0];
528              
529             # if they want to skip database configuration, we will clear/skip the database values
530 0 0 0       if ($prompt_key =~ /database/ && $$results{use_database} eq 'N') {
531 0           $$results{$prompt_key} = '';
532 0           next;
533             }
534            
535             # password mode?
536            
537 0           $the_prompt = $$prompt_set[1];
538 0 0         if ($$prompt_set[2]) {
539 0 0         if ($$prompt_set[0] =~ /password/i) {
540 0           $the_prompt .= ' [Default: Stored value]';
541             } else {
542 0           $the_prompt .= ' [Default: '.$$prompt_set[2].']';
543             }
544             }
545 0           $the_prompt .= ' : ';
546            
547 0 0 0       if ($$prompt_set[0] =~ /password/i && !$$prompt_set[2]) {
    0 0        
    0          
548 0           $$results{$prompt_key} = prompt $the_prompt, -echo=>'*', -stdio, -v, -must => { 'provide a value' => qr/\S/};
549              
550             } elsif ($$prompt_set[0] =~ /password/i) {
551 0           $$results{$prompt_key} = prompt $the_prompt, -echo=>'*', -stdio, -v;
552              
553             } elsif ($$prompt_set[1] =~ /required/i && !$$prompt_set[2]) {
554 0           $$results{$prompt_key} = prompt $the_prompt, -stdio, -v, -must => { 'provide a value' => qr/\S/};
555              
556             } else {
557 0           $$results{$prompt_key} = prompt $the_prompt, -stdio, -v;
558             }
559            
560             # Y or N means Y or N
561 0 0         if ($$prompt_set[1] =~ /Y or N/) {
562 0           $$results{$prompt_key} = uc( $$results{$prompt_key} ) ; # might have typed 'y'
563 0 0         if ($$results{$prompt_key} !~ /^(Y|N)$/) { # if not exactly right, use the default or just N
564 0   0       $$results{$prompt_key} = $$prompt_set[2] || 'N';
565             }
566             }
567            
568             # accept defaults
569 0   0       $$results{$prompt_key} ||= $$prompt_set[2];
570              
571             }
572              
573 0           return $results;
574             }
575              
576             # method to start and stop plack
577             sub plack_controller {
578 0     0 0   my ($self,@args) = @_;
579              
580 0           my $pid_file = $self->{pepper_directory}.'/log/pepper.pid';
581            
582 0           my $dev_reload = '';
583 0 0         $dev_reload = '-R '.$self->{pepper_directory}.'/lib' if $args[2];
584            
585 0 0         if ($args[0] eq 'start') {
    0          
    0          
586              
587 0   0       my $max_workers = $args[1] || 10;
588              
589 0           system(qq{start_server --enable-auto-restart --auto-restart-interval=300 --port=5000 --dir=$self->{pepper_directory}/psgi --log-file=$self->{pepper_directory}/log/pepper.log --daemonize --pid-file=$pid_file -- plackup -s Gazelle --max-workers=$max_workers -E deployment $dev_reload pepper.psgi});
590            
591             } elsif ($args[0] eq 'stop') {
592            
593 0           system(qq{kill -TERM `cat $pid_file`});
594              
595             } elsif ($args[0] eq 'restart') {
596              
597 0           system(qq{kill -HUP `cat $pid_file`});
598            
599             }
600              
601             }
602              
603             1;
604              
605             =head1 NAME
606              
607             Pepper::Commander
608              
609             =head1 DESCRIPTION / PURPOSE
610              
611             This package provides all the functionality for the 'pepper' command script, which
612             allows you to configure and start/stop the Pepper Plack service.
613              
614             =head2 sudo pepper setup
615              
616             This is the configuration mode. The Pepper workspace will be created as a 'pepper'
617             directory within your home directory, aka $ENV{HOME}/pepper, unless it already exists.
618             You will be prompted for the configuration options, and your configuration file will
619             be created or overwritten.
620              
621             =head2 pepper test-db
622              
623             This will perform a basic connection / query test on the database config you provided
624             via 'pepper setup'.
625              
626             =head2 pepper set-endpoint [URI] [PerlModule]
627              
628             This creates an endpoint mapping in Pepper to tell Plack how to dispatch incoming
629             requests. The first argument is a URI and the second is a target Perl module for
630             handing GET/POST requests to the URI. If these two arguments are not given, you
631             will be prompted for the information. Use 'default' for the URI to set a default
632             endpoint handler.
633              
634             If the Perl module does not exist under $ENV{HOME}/pepper/lib, an initial version will be created.
635              
636             =head2 pepper list-endpoints
637              
638             This will output your configured endpoint mappings.
639              
640             =head2 pepper test-endpoint [URI]
641              
642             This will run Plack::Test against the URI's endpoint to see that it will return 200 OK.
643             This is not a test of functionality, just a test that the endpoint executes and returns 200.
644              
645             =head2 pepper delete-endpoint [URI]
646              
647             Removes an endpoint mapping from Pepper. The Perl module will not be deleted.
648              
649             =head2 pepper start [#Workers] [dev-reload]
650              
651             Attempts to start the Plack service. Provide an integer for the #Workers to spcify the
652             maximum number of Plack processes to run. The default is 10.
653              
654             If you indicate a number of workers plus 'dev-reload' as the third argument, Plack
655             will be started with the auto-reload option to auto-detect changes to your code.
656             If that is not provided, you will need to issue 'pepper restart' to put your code
657             changes into effect. Enabling dev-reload will slow down Plack significantly, so it
658             is only appropriate for development environments.
659              
660             =head2 pepper restart
661              
662             Restarts the Plack service and put your code changes into effect.