App::Phoebe::Web - serve Phoebe wiki pages via the web


Phoebe doesn’t have to live behind another web server like Apache or nginx. It can be a (simple) web server, too!

This package gives web visitors read-only access to Phoebe. HTML is served via HTTPS on the same port as everything else, i.e. 1965 by default.

There is no configuration. Simply add it to your config file:

    use App::Phoebe::Web;

Beware: these days browser will refuse to connect to sites that have self-signed certificates. You’ll have to click buttons and make exceptions and all of that, or get your certificate from Let’s Encrypt or the like. That in turn is aggravating for your Gemini visitors, since you are changing the certificate every few months.

If you want to allow web visitors to comment on your pages, see App::Phoebe::WebComments; if you want to allow web visitors to edit pages, see App::Phoebe::WebEdit.

You can serve the wiki both on the standard Gemini port and on the standard HTTPS port:

    phoebe --port=443 --port=1965

Note that 443 is a priviledge port. Thus, you either need to grant Perl the permission to listen on a priviledged port, or you need to run Phoebe as a super user. Both are potential security risk, but the first option is much less of a problem, I think.

If you want to try this, run the following as root:

    setcap 'cap_net_bind_service=+ep' $(which perl)

Verify it:

    getcap $(which perl)

If you want to undo this:

    setcap -r $(which perl)

If you don't do any of the above, you'll get a permission error on startup: "Mojo::Reactor::Poll: Timer failed: Can't create listen socket: Permission denied…" You could, of course, always use a traditional web server like Apache as a front-end, proxying all requests to your site on port 443 to port 1965. This server config also needs access to the same certificates that Phoebe is using, for port 443. The example below doesn’t rewrite /.well-known URLs because these are used by Let’s Encrypt and others.

    <VirtualHost *:80>
        RewriteEngine on
        # Do not redirect /.well-known URL
        RewriteCond %{REQUEST_URI} !^/\.well-known/
        RewriteRule ^/(.*) https://%{HTTP_HOST}:1965/$1
    <VirtualHost *:443>
        RewriteEngine on
        # Do not redirect /.well-known URL
        RewriteCond %{REQUEST_URI} !^/\.well-known/
        RewriteRule ^/(.*) https://%{HTTP_HOST}:1965/$1
        SSLEngine on
        SSLCertificateFile      /var/lib/dehydrated/certs/
        SSLCertificateKeyFile   /var/lib/dehydrated/certs/
        SSLCertificateChainFile /var/lib/dehydrated/certs/
        SSLVerifyClient None

Here’s an example where we wrap one the subroutines in App::Phoebe::Web in order to change the default CSS that gets served. We keep a code reference to the original, substitute our own, and when it gets called, it first calls the old code to print some CSS, and then we append some CSS of our own. Also note how we import $log.

    # tested by t/example-dark-mode.t
    package App::Phoebe::DarkMode;
    use App::Phoebe qw($log);
    use App::Phoebe::Web;
    no warnings qw(redefine);

    # fully qualified because we're in a different package!
    *old_serve_css_via_http = \&App::Phoebe::Web::serve_css_via_http;
    *App::Phoebe::Web::serve_css_via_http = \&serve_css_via_http;

    sub serve_css_via_http {
      my $stream = shift;
      $log->info("Adding more CSS via HTTP (for dark mode)");
    @media (prefers-color-scheme: dark) {
       body { color: #eeeee8; background-color: #333333; }
       a:link { color: #1e90ff }
       a:hover { color: #63b8ff }
       a:visited { color: #7a67ee }