#!/usr/bin/env perl
use strict;
use warnings;

use PAGI::Runner;

PAGI::Runner->new->run(@ARGV);

__END__

=encoding UTF-8

=head1 NAME

pagi-server - PAGI Reference Server CLI

=head1 SYNOPSIS

    pagi-server [options] [app] [key=value ...]

    # Serve current directory (default)
    pagi-server

    # Serve a specific directory
    pagi-server PAGI::App::Directory root=/var/www

    # Run a PAGI app file
    pagi-server ./app.pl
    pagi-server --app app.pl        # legacy syntax

    # With options
    pagi-server -p 8080 PAGI::App::Directory root=/var/www
    pagi-server -w 4 ./myapp.pl

=head1 DESCRIPTION

pagi-server is the command-line interface for running PAGI applications.
It can load apps from files or from installed C<PAGI::App::*> modules.

=head1 OPTIONS

=over 4

=item --lib, -I PATH

Add PATH to C<@INC> before loading the app. Can be specified multiple
times to add multiple paths, similar to C<perl -I>.

    pagi-server -I ./lib -I ./vendor/lib ./app.pl

=item --app, -a FILE

Path to .pl/.psgi file returning PAGI app coderef. Legacy option for
backward compatibility; you can also just pass the file as an argument.

=item --host, -h HOST

Bind address (default: 127.0.0.1, localhost only)

The default only accepts connections from localhost, which is B<secure by
default> for development. For headless servers or production deployments
where remote clients need to connect:

    # Bind to all IPv4 interfaces
    pagi-server --host 0.0.0.0 ./app.pl

    # Bind to specific interface
    pagi-server --host 192.168.1.100 ./app.pl

=item --port, -p PORT

Bind port (default: 5000)

=item --workers, -w NUM

Number of worker processes (default: 1)

=item --max-requests NUM

Maximum number of requests a worker process will handle before restarting.
After serving this many requests, the worker gracefully shuts down and the
parent spawns a replacement.

B<Default:> 0 (disabled - workers run indefinitely)

B<When to use:> Long-running deployments where gradual memory growth is a
concern, or applications with known memory leaks.

B<Note:> Only applies in multi-worker mode (C<--workers> > 1). In single-worker
mode, this setting is ignored.

=item --max-connections NUM

Maximum number of concurrent connections per worker before returning
HTTP 503 Service Unavailable. This prevents "Too many open files" crashes.

B<Default:> 0 (auto-detect from ulimit - 50)

B<Behavior:> When at capacity, new connections receive a 503 response with
a C<Retry-After: 5> header. The TCP backlog queues additional connections.

B<When to adjust:>

=over 4

=item * Getting 503s under load? Increase ulimit or set higher limit

=item * Want predictable capacity? Set explicit limit matching your ulimit

=item * Memory constrained? Lower the limit to reduce concurrent request memory

=back

B<Note:> In multi-worker mode, this limit applies per-worker. With
C<--workers 4 --max-connections 100>, total capacity is 400 connections.

=item --max-body-size NUM

Maximum request body size in bytes. Requests exceeding this limit receive
HTTP 413 (Payload Too Large).

B<Default:> 10,000,000 (10MB)

B<Special value:> 0 means unlimited (not recommended for public servers).

B<When to adjust:>

=over 4

=item * File uploads? Increase to 50-100MB as needed

=item * API with large JSON? Increase to match expected payload sizes

=item * Behind reverse proxy? Can set to 0 if proxy enforces its own limit

=back

=item --log-level LEVEL

Set the logging verbosity level. Messages below this level are suppressed.

B<Levels:> debug, info, warn, error

B<Default:> info

=back

=head2 Production Options

=over 4

=item B<-D>, B<--daemonize>

Fork to background and run as a daemon. Errors during startup (including
binding to port) are reported before daemonizing.

=item B<--pid> FILE

Write the process ID to FILE. The file is removed on clean shutdown.
Useful for init scripts: C<kill $(cat /var/run/pagi.pid)>

=item B<--user> USER

After binding to the port, drop privileges to run as USER. Requires
starting the server as root. Commonly used with privileged ports (80, 443).

=item B<--group> GROUP

After binding to the port, drop privileges to run as GROUP. If not
specified but --user is, uses the user's primary group.

=back

=over 4

=item --listener_backlog -b

Listener queue size. No default, if left blank then the
server sets a default that is rational for itself.

=item --timeout

Seconds before we timeout the request. If left blank will
default to whatever is default for the server.

=item --loop, -l BACKEND

Event loop backend (Poll, EV, Epoll, UV). Default is auto-detect.

=item --ssl-cert FILE

Path to SSL certificate (enables HTTPS)

=item --ssl-key FILE

Path to SSL private key

=item --access-log FILE

Path to access log file (default: STDERR)

=item --no-access-log

Disable access logging entirely. Eliminates per-request I/O overhead,
improving throughput by 5-15% depending on workload. Useful for
benchmarking or when access logs are handled by a reverse proxy.

=item --reuseport

Enable SO_REUSEPORT mode for multi-worker servers. Each worker creates
its own listening socket with SO_REUSEPORT.

B<Linux 3.9+>: The kernel load-balances incoming connections across workers,
reducing accept() contention and improving p99 latency under high concurrency.

B<macOS/BSD>: SO_REUSEPORT allows binding but does NOT provide kernel load
balancing. May actually decrease performance - benchmark before using.

=item --max-receive-queue NUM

Maximum number of messages in the WebSocket receive queue before the
connection is closed with code 1008 (Policy Violation). This is a DoS
protection mechanism to prevent memory exhaustion from slow consumers.

B<Default:> 1000 messages

B<Unit:> Message count, not bytes. Each WebSocket frame counts as one message.

B<Tuning:> Lower to 100-500 for slow message processing. Raise to 5000-10000
for trusted high-throughput clients. Memory risk per connection is approximately
max_receive_queue × average_message_size.

=item --max-ws-frame-size NUM

Maximum size in bytes for a single WebSocket frame payload. When a client
sends a frame larger than this limit, the connection is closed.

B<Default:> 65536 bytes (64KB)

B<Unit:> Bytes (not messages). This controls individual frame size.

B<Tuning:> For chat/control messages, default 64KB is sufficient. For file
uploads via WebSocket, increase to 1-16MB. Memory impact: each connection
can buffer up to this amount during frame parsing.

=item --quiet, -q

Suppress startup banner

=item --help

Show this help

=back

=head2 Signal Handling

=over 4

=item B<TERM>, B<INT>

Graceful shutdown. Stops accepting new connections, waits for existing
requests to complete, then exits.

=item B<HUP>

Graceful restart (multi-worker mode only). Terminates all current workers
and spawns fresh replacements. Useful for config/code changes.

=item B<TTIN>

Increase worker count by 1 (multi-worker mode only).

=item B<TTOU>

Decrease worker count by 1 (multi-worker mode only). Maintains at least
1 worker.

=back

=head1 APP SPECIFICATION

The app can be specified in three ways:

=head2 Module Name

If the argument contains C<::>, it's treated as a Perl module name:

    pagi-server PAGI::App::Directory root=/var/www

The module must have C<new()> and C<to_app()> methods. Constructor
arguments are passed as C<key=value> pairs.

=head2 File Path

If the argument contains C</> or ends with C<.pl>/C<.psgi>:

    pagi-server ./app.pl
    pagi-server /path/to/myapp.psgi

The file must return a PAGI app coderef.

=head2 Default

If no app is specified, serves the current directory:

    pagi-server
    # equivalent to: pagi-server PAGI::App::Directory root=.

=head1 EXAMPLES

    # Serve current directory on default port
    pagi-server

    # Serve /var/www on port 8080
    pagi-server -p 8080 PAGI::App::Directory root=/var/www

    # Run an app file with HTTPS
    pagi-server -a myapp.pl -p 3000 --ssl-cert cert.pem --ssl-key key.pem

    # Run with multiple workers
    pagi-server -w 4 ./myapp.pl

    # Headless server / remote access (bind to all interfaces)
    pagi-server --host 0.0.0.0 -p 8080 ./myapp.pl

    # Production: all interfaces, workers, daemonized
    pagi-server --host 0.0.0.0 -p 80 -w 4 -D --pid /var/run/pagi.pid ./myapp.pl

    # Add library paths (like perl -I)
    pagi-server -I ./lib -I ./local/lib ./myapp.pl

    # Use EV event loop for better performance
    pagi-server --loop EV ./myapp.pl

    # Maximum performance (no access logging)
    pagi-server --no-access-log -w 8 ./myapp.pl

    # Optimal macOS performance (EV + kqueue)
    LIBEV_FLAGS=8 pagi-server --loop EV --no-access-log ./myapp.pl

    # High concurrency with SO_REUSEPORT (reduces accept contention)
    pagi-server --reuseport -w 8 ./myapp.pl

    # Workers restart after 10k requests (prevents memory growth)
    pagi-server -w 4 --max-requests 10000 ./myapp.pl

    # Limit max connections (prevents FD exhaustion)
    pagi-server --max-connections 200 ./myapp.pl

    # Enable debug logging
    pagi-server --log-level debug ./myapp.pl

    # Or via environment variable
    IO_ASYNC_LOOP=EV pagi-server ./myapp.pl

    # Run a proxy app
    pagi-server PAGI::App::Proxy target=http://backend:3000

    # Directory listing with hidden files
    pagi-server PAGI::App::Directory root=/home show_hidden=1

=head1 BUNDLED APPS

The following C<PAGI::App::*> modules are available:

=over 4

=item PAGI::App::Directory

Serve static files with directory listings.

    pagi-server PAGI::App::Directory root=/var/www

=item PAGI::App::File

Serve static files (no directory listings).

    pagi-server PAGI::App::File root=/var/www

=item PAGI::App::Proxy

HTTP reverse proxy.

    pagi-server PAGI::App::Proxy target=http://backend:3000

=item PAGI::App::Redirect

URL redirector.

    pagi-server PAGI::App::Redirect location=https://example.com

=item PAGI::App::URLMap

Mount multiple apps at different paths.

=item PAGI::App::Cascade

Try multiple apps in sequence.

=back

See individual module documentation for available options.

=head1 ENVIRONMENT

=over 4

=item IO_ASYNC_LOOP

Specifies which IO::Async::Loop backend to use. The default is auto-detection.
For better performance, especially with many concurrent connections:

    IO_ASYNC_LOOP=EV pagi-server ./myapp.pl      # Cross-platform (libev)
    IO_ASYNC_LOOP=Epoll pagi-server ./myapp.pl   # Linux-specific

The C<--loop> option takes precedence over this environment variable.

=item LIBEV_FLAGS

When using the EV backend, this controls which libev backend is used internally.
By default, EV may choose a suboptimal backend (select instead of kqueue on macOS).

    # Force kqueue on macOS for optimal performance (~6% faster)
    LIBEV_FLAGS=8 pagi-server --loop EV ./myapp.pl

Values: 1=select, 2=poll, 4=epoll (Linux), 8=kqueue (macOS/BSD)

=back

=head1 EVENT LOOP BACKENDS

=over 4

=item Poll

Default fallback. Uses poll() system call. Works everywhere but slower
with many connections.

=item EV

High-performance backend using libev. Recommended for production.
Install: C<cpanm IO::Async::Loop::EV EV>

=item Epoll

Linux-specific backend using epoll. O(1) complexity.
Install: C<cpanm IO::Async::Loop::Epoll>

=item UV

Backend using libuv (Node.js's event library). Cross-platform.
Install: C<cpanm IO::Async::Loop::UV UV>

=back

=head1 SEE ALSO

L<PAGI::Runner>, L<PAGI::Server>, L<PAGI::App::Directory>

=head1 AUTHOR

John Napiorkowski E<lt>jjnapiork@cpan.orgE<gt>

=head1 LICENSE

This software is licensed under the same terms as Perl itself.

=cut
