1package Emplacken; 2BEGIN { 3 $Emplacken::VERSION = '0.01'; 4} 5 6use Moose; 7 8use Class::Load qw( is_class_loaded try_load_class ); 9use Config::Any; 10use Emplacken::App; 11use Emplacken::Types qw( ArrayRef Bool Dir File ); 12use Getopt::Long; 13use List::AllUtils qw( first ); 14 15with 'MooseX::Getopt::Dashes'; 16 17Getopt::Long::Configure('pass_through'); 18 19has dir => ( 20 is => 'ro', 21 isa => Dir, 22 coerce => 1, 23 default => '/etc/emplacken', 24 documentation => 25 'The directory which contains your emplacken config files', 26); 27 28has file => ( 29 is => 'ro', 30 isa => File, 31 coerce => 1, 32 predicate => '_has_file', 33 documentation => 'You can supply a single file instead of a directory', 34); 35 36has verbose => ( 37 is => 'ro', 38 isa => Bool, 39 default => 1, 40 documentation => 'Controls whether some commands print output to stdout', 41); 42 43has __psgi_apps => ( 44 traits => ['Array'], 45 is => 'ro', 46 isa => ArrayRef ['Emplacken::App'], 47 init_arg => undef, 48 lazy => 1, 49 builder => '_build_psgi_apps', 50 handles => { 51 _psgi_apps => 'elements', 52 _app_count => 'count', 53 }, 54); 55 56sub run { 57 my $self = shift; 58 59 my $command = $self->extra_argv()->[0] || 'start'; 60 my $meth = q{_} . $command; 61 62 unless ( $self->can($meth) ) { 63 die "Invalid command for emplacken: $command\n"; 64 } 65 66 unless ( $self->_app_count() ) { 67 if ( $self->_has_file() ) { 68 die $self->file() . " is not a PSGI application config file\n"; 69 } 70 else { 71 die "Did not find any PSGI application config files in " 72 . $self->dir() . "\n"; 73 } 74 } 75 76 if ( $self->$command() ) { 77 _exit(0); 78 } 79 else { 80 _exit(1); 81 } 82} 83 84# This is a sub so we can override it for testing 85sub _exit { 86 exit shift; 87} 88 89sub _start { 90 my $self = shift; 91 92 return $self->_run_for_all_apps('start'); 93} 94 95sub _stop { 96 my $self = shift; 97 98 return $self->_run_for_all_apps('start'); 99} 100 101sub _restart { 102 my $self = shift; 103 104 return $self->_run_for_all_apps('stop') 105 + $self->_run_for_all_apps('start'); 106} 107 108sub _run_for_all_apps { 109 my $self = shift; 110 my $meth = shift; 111 112 my $failed = 0; 113 for my $app ( $self->_psgi_apps() ) { 114 115 my $result = $app->$meth() ? 'OK' : 'failed'; 116 117 my $message = sprintf( 118 " %50s ... [%s]\n", 119 "${meth}ing " . $app->name(), 120 $result 121 ); 122 123 $self->_maybe_print($message); 124 } 125 126 return !$failed; 127} 128 129sub _status { 130 my $self = shift; 131 132 for my $app ( $self->_psgi_apps() ) { 133 printf( 134 " %50s ... [%s]\n", 135 $app->name(), 136 $app->is_running() ? 'running' : 'stopped' 137 ); 138 } 139} 140 141sub _build_psgi_apps { 142 my $self = shift; 143 144 my @files 145 = $self->_has_file() 146 ? $self->file() 147 : grep { ! $_->is_dir } $self->dir()->children(); 148 149 return [ 150 map { $self->_build_app_from_file($_) } 151 grep {/\.conf/} grep {-s} @files 152 ]; 153} 154 155sub _build_app_from_file { 156 my $self = shift; 157 my $file = shift; 158 159 my $cfg = Config::Any->load_files( 160 { 161 files => [$file], 162 flatten_to_hash => 1, 163 use_ext => 0, 164 } 165 ); 166 167 die "$file does not seem to contain any configuration\n" 168 unless $cfg->{$file}; 169 170 $cfg = $cfg->{$file}; 171 172 die "$file does not contain a server key" 173 unless defined $cfg->{server} && length $cfg->{server}; 174 175 my $app_class = first { try_load_class($_) } ( 176 'Emplacken::App::' . $cfg->{server}, 177 'Emplacken::App' 178 ); 179 180 return $app_class->new( file => $file, %{$cfg} ); 181} 182 183sub _maybe_print { 184 my $self = shift; 185 my $msg = shift; 186 187 return unless $self->verbose(); 188 189 print $msg; 190} 191 1921; 193 194#ABSTRACT: Manage multiple plack apps with a directory of config files 195 196 197 198=pod 199 200=head1 NAME 201 202Emplacken - Manage multiple plack apps with a directory of config files 203 204=head1 VERSION 205 206version 0.01 207 208=head1 SYNOPSIS 209 210 emplacken --dir /etc/emplacken start 211 212 emplacken --dir /etc/emplacken stop 213 214=head1 DESCRIPTION 215 216B<NOTE: This is all still experimental. Things may change in the future.> 217 218Emplacken is a tool for managing a set of L<Plack> applications based on 219config files. It also adds support for privilege dropping and error logs to 220those Plack servers that don't support these features natively. 221 222It works be reading a config file and using that to I<generate> a PSGI 223application file based on your config. It knows how to generate L<Catalyst>, 224L<Mojo>, and L<Mason> app files natively. For other apps, or more complicated 225setups, you can supply a template to Emplacken and it will use that to 226generate the PSGI app. 227 228=head1 COMMAND LINE OPTIONS 229 230The C<emplacken> command accepts either a C<--dir> or C<--file> option. If you 231don't specify either, it defaults to using the F</etc/emplacken> directory. 232 233You must also pass a command, one of C<start>, C<stop>, C<restart>, or 234C<status>. 235 236Finally, you can specify a C<--verbose> or C<--no-verbose> flag. This 237determines whether the C<start> and C<stop> command print to stdout. The 238C<status> command I<always> prints to stdout. 239 240=head1 CONFIG FILES 241 242This module uses L<Config::Any> to read config files, so you have a number of 243choices for config file syntax. These examples will use either INI or JSON 244syntax. 245 246All the config options should be in a single top-level section. 247 248=head2 Common Config Options 249 250These options are shared for all servers and code builders. 251 252For config file styles that don't support multiple values for a single option, 253you can use a comma-separated string to set multiple options. 254 255=head3 server 256 257This will be passed to the C<plackup> command to tell it what server class to 258use, for example L<Starlet> or L<Corona>. If you specify C<Starman>, then the 259C<starman> command will be used instead of C<plackup>. 260 261You can also use the value "plackup" here, which will let the Plack code pick 262the server automagically. 263 264This is required. 265 266=head3 builder 267 268The code builder to use. Currently, this can be one of L<Catalyst>, L<Mason>, 269L<Mojo>, or L<FromTemplate>. Each code builder support different config 270options. See below for details. 271 272=head3 pid_file 273 274The location of the pid file for this application. 275 276This is required. 277 278=head3 include 279 280A set of include directories to be passed to C<plackup>. You can specify 281multiple values. 282 283=head3 modules 284 285A set of modules to be passed to C<plackup>. You can specify multiple 286values. These modules will be preloaded by C<plackup>. 287 288=head3 listen 289 290This can be "HOST", "HOST:PORT", ":PORT", or a path for a Unix socket. This 291can be set multiple times, but some servers may not support multiple values. 292 293=head3 user 294 295If this is set then Emplacken will attempt to become this user before starting 296the PSGI app. 297 298=head3 group 299 300If this is set then Emplacken will attempt to become this group before 301starting the PSGI app. 302 303=head3 middleware 304 305This can be one or more middleware modules that should be enabled. Note that 306there is no way to pass config options to these modules (yet?). You can 307specify multiple values. 308 309=head3 reverse_proxy 310 311If this is true, then the L<Plack::Middleware::ReverseProxy> module is enabled 312for requests coming from 127.0.0.1. 313 314=head3 access_log 315 316If this is set to a file, then the L<Plack::Middleware::AccessLog> module is 317enabled. It will log to the specified file. 318 319=head3 access_log_format 320 321This can be used to change the access log format. 322 323=head3 error_log 324 325If this is set, then the generated PSGI app will tie C<STDERR> and log to a 326file. The log format is like Apache's error log, so you'll get something like 327this: 328 329 [Sun Dec 19 00:42:32 2010] [error] [client 1.2.3.4] Some error 330 331Any error output from Perl will be tweaked so that it fits on a single 332line. All non-printable characters will be replaced by their hex value. 333 334=head2 Starman Options 335 336If you are using the Starman server, there are several more options you can 337set in the config file. 338 339=head3 workers 340 341The number of worker processes to spawn. 342 343=head3 backlog 344 345The maximum number of backlogged listener sockets allowed. 346 347=head3 max_requests 348 349The maximum number of requests per child. 350 351=head3 preload_app 352 353If this is true, then your PSGI app is preloaded by Starman before any child 354processes are forked. 355 356=head3 disable_keepalive 357 358If this is true, then keepalive is disabled. 359 360=head2 Catalyst Options 361 362If you are using the Catalyst code builder, you must specify an C<app_class> 363config option. This is the name of the class for your web application. 364 365=head2 Mason Options 366 367If you are using the Mason code builder, you must specify C<comp_root> and 368C<data_dir> config options. 369 370=head2 Mojo Options 371 372If you are using the Mojo code builder, you must specify an C<app_class> 373config option. This is the name of the class for your web application. 374 375=head2 FromTemplate 376 377If you are using the FromTemplate code builder, you must specify a C<template> 378config option. This should be the file which contains the PSGI app template to 379use. 380 381=head2 Template Variables 382 383You can provide your own L<Text::Template> template file for Emplacken to use 384as a template when building the PSGI application file. The builder will set 385the code delimeters to C<{{> and C<}}>. 386 387You should design your template to expect several variables: 388 389=over 4 390 391=item * {{$modules}} 392 393This will be a set of C<use> statements loading any needed modules. This will 394include modules specified in the C<modules> config key, and well as additional 395modules Emplacken may require in your PSGI application file. 396 397=item * {{$pre}} 398 399This will be a chunk of code that should come before any setup code you need 400to write, and before the C<builder> block. 401 402=item * {{$builder_pre}} 403 404This should go immediately inside your C<builder> block. 405 406=item * {{$mw}} 407 408This will be a chunk of code that enables middleware. It will include 409middleware specified by the C<middleware> config option as well as anything 410else Emplacken needs (like the access log code). 411 412=item * {{$post}} 413 414This will be a chunk of code that should come after the C<builder> block at 415the end of the file. 416 417=back 418 419Here is an example template for a Catalyst application called C<MyApp>: 420 421 use strict; 422 use warnings; 423 424 use MyApp; 425 {{$modules}} 426 {{$pre}} 427 428 MyApp->setup_engine('PSGI'); 429 430 builder { 431 {{ $builder_pre }} 432 {{ $mw }} 433 sub { MyApp->run(@_) }; 434 }; 435 436 {{$post}} 437 438=head1 DONATIONS 439 440If you'd like to thank me for the work I've done on this module, please 441consider making a "donation" to me via PayPal. I spend a lot of free time 442creating free software, and would appreciate any support you'd care to offer. 443 444Please note that B<I am not suggesting that you must do this> in order for me 445to continue working on this particular software. I will continue to do so, 446inasmuch as I have in the past, for as long as it interests me. 447 448Similarly, a donation made in this way will probably not make me work on this 449software much more, unless I get so many donations that I can consider working 450on free software full time, which seems unlikely at best. 451 452To donate, log into PayPal and send money to autarch@urth.org or use the 453button on this page: L<http://www.urth.org/~autarch/fs-donation.html> 454 455=head1 BUGS 456 457Please report any bugs or feature requests to C<bug-emplacken@rt.cpan.org>, or 458through the web interface at L<http://rt.cpan.org>. I will be notified, and 459then you'll automatically be notified of progress on your bug as I make 460changes. 461 462=head1 AUTHOR 463 464Dave Rolsky <autarch@urth.org> 465 466=head1 COPYRIGHT AND LICENSE 467 468This software is Copyright (c) 2010 by Dave Rolsky. 469 470This is free software, licensed under: 471 472 The Artistic License 2.0 473 474=cut 475 476 477__END__ 478 479