2018-10-08 Setting up Oddmuse 6

Here’s what I needed to do in order to get the Oddmuse 6 wiki up an running.

Oddmuse 6

I downloaded and compiled the latest Rakudo Star sources (2018.06).

Rakudo Star

Install cro and Oddmuse 6:

cro

zef install --/test cro
zef install Oddmuse6

🔥 When installing dependencies using `zef` as shown, you could be running into an OpenSSL issue even if you have the correct development libraries installed. On Debian, you need `libssl-dev` but apparently versions 1.1.0f and 1.1.0g won’t work. See issue ​#34. You could decide to ignore SSL support and opt to have a web server act as a proxy which provides SSL. That’s what I intend to do. In which case there is a terrible workaround available: run `zef install --force-test IO::Socket::Async::SSL` before you `zef install Oddmuse6`.

issue ​#34

Create a new application. Remember that installing `cro` printed a message telling you where the binary got installed. I’m assuming you added `$HOME/rakudo/share/perl6/site/bin` to your `PATH`.

Start with a stub and accept all the defaults:

cro stub http oddmuse6 oddmuse6

Now edit `oddmuse6/services.p6` and replace `use Routes` with `use Oddmuse::Routes`.

You can delete the `oddmuse6/lib/Routes.pm6` which `cro stub` generated for you.

Your default wiki directory is `oddmuse6/wiki`, so we need to tell `cro` to ignore it. If you don’t, you’ll confuse `cro` to no end as soon as you start editing files! Add the following section section to your in `oddmuse6/.cro.yml` file:

ignore:
  - wiki/

Run it:

cd oddmuse6
cro run

Check it out by visiting `http://localhost:20000`. Your wiki is ready! 🙃

But this is not enough. I needed to get my own domain. My name service provider for `oddmuse.org` is Gandi so I logged in, found the entry and added a new subdomain called `next` pointing at the same IP, basically a copy of the `www` subdomain.

Next, I tell Apache about it. I’m using Dehydrated to get my `oddmuse.org` certificates from *Let’s Encrypt* and I’m just going to reuse them.

Dehydrated

In the file `/etc/dehydrated/domains.txt` I added the new `next.oddmuse.org` domain:

oddmuse.org www.oddmuse.org next.oddmuse.org

Then I ran `/usr/bin/dehydrated -c` as root to check and possibly renew all my certificates.

I’m using Apache as my webserver. I edited `/etc/apache2/sites-enabled/500-oddmuse.org.conf` and added two new sections for `next.oddmuse.org`.

Apache

The first redirects HTTP traffic to HTTPS:

<VirtualHost *:80>
    ServerName next.oddmuse.org
    Redirect permanent / https://next.oddmuse.org/
</VirtualHost>

The second redirects all HTTPS traffic it receives back to HTTP on port 20000, which is where Oddmuse 6 is listening.

<VirtualHost *:443>
    ServerAdmin alex@oddmuse.org
    ServerName next.oddmuse.org

    RewriteEngine on
    RewriteCond "%{HTTP_USER_AGENT}" "Mastodon"
    RewriteRule ".*" "-" [redirect=403,last]

    SSLEngine on
    SSLCertificateFile      /var/lib/dehydrated/certs/oddmuse.org/cert.pem
    SSLCertificateKeyFile   /var/lib/dehydrated/certs/oddmuse.org/privkey.pem
    SSLCertificateChainFile /var/lib/dehydrated/certs/oddmuse.org/chain.pem
    SSLVerifyClient None

    ProxyPass /             http://next.oddmuse.org:20000/

</VirtualHost>

Thus, all connections to the wiki are encrypted. What about people connecting to port 20000 directly? Well, I have a file called `/etc/apache2/conf-enabled/security.conf` which has a few settings including this one:

# Setting this header will prevent access via HTTP.
# Requires mod_headers to be enabled.
# See https://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-6.1.2
# The number of seconds is the equivalent of one year.
#
Header set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

The rewrite rule for Mastodon is a simple defence against the spikes I get when I share links to my sites. In a second, all the servers seeing the link attempt to fetch a preview image and in order to avoid overloads, I just disallow these.

So now, finally, requests end up as simple HTTP on 20000. Unfortunately, the wiki is listening on `localhost`, not `next.oddmuse.org`.

So I wrote a little wrapper script to set all the options, write a PID file and all that. I called it `~/bin/oddmuse6`.

#!/bin/bash
pidfile=$HOME/oddmuse6/nohup.pid
if test -f $pidfile; then
    echo Killing $(cat $pidfile)
    kill -SIGTERM $(cat $pidfile)
fi

export ODDMUSE_MENU="Home, Changes, About"
export ODDMUSE_QUESTION="Name a colour of the rainbow."
export ODDMUSE_ANSWER="red, orange, yellow, green, blue, indigo, violet"
export ODDMUSE_SECRET="rainbow-unicorn"
export ODDMUSE_HOST="next.oddmuse.org"
export ODDMUSE_PORT=20000

cd $HOME/oddmuse6
rm nohup.out
nohup /home/alex/rakudo/bin/perl6 service.p6 &
tail nohup.out
echo $! > $pidfile

But that still isn’t enough because `ODDMUSE_HOST` and `ODDMUSE_PORT` actually don’t match the environment variables used in `oddmuse6/service.p6`. Each `cro` service gets to environment variables that determine its *host* and its *port*. Their names depend on the name you provided when you called `cro stub`. If you called it `oddmuse6` like I did in the example above, the two environment variables you need are called `ODDMUSE6_HOST` and `ODDMUSE6_PORT`. I just changed them to `ODDMUSE_HOST` and `ODDMUSE_PORT` in `service.p6`.

As you can see from the shell script, I’m not even using `cro run` to run my wiki. I’m using `perl6` directly. I’ll have to think about that. I suspect there are benefits of using `cro run` when you use multiple applications. At the moment I don’t, so `perl6` it is.

Since I’m not using `cro run` you could delete all these other files, actually. You really just need `service.p6` and the `wiki` subdirectory.

The content of `service.p6` is simple:

use Cro::HTTP::Log::File;
use Cro::HTTP::Server;
use Oddmuse::Routes;

my Cro::Service $http = Cro::HTTP::Server.new(
    http => <1.1>,
    host => %*ENV<ODDMUSE_HOST> ||
        die("Missing ODDMUSE_HOST in environment"),
    port => %*ENV<ODDMUSE_PORT> ||
        die("Missing ODDMUSE_PORT in environment"),
    application => routes(),
    after => [
        Cro::HTTP::Log::File.new(logs => $*OUT, errors => $*ERR)
    ]
);
$http.start;
say "Listening at http://%*ENV<ODDMUSE_HOST>:%*ENV<ODDMUSE_PORT>";
react {
    whenever signal(SIGINT) {
        say "Shutting down...";
        $http.stop;
        done;
    }
}

Check it out: Oddmuse 6.

Oddmuse 6

​#Perl 6 ​#Oddmuse 6