Installing Urbit on Ubuntu 20.04 LTS with AWS Lightsail


The official guide for installing Urbit on a Linux server is accurate enough and up to date. At the same time, it’s easy for documentation on Urbit at this stage in its development to get deprecated substantially, and running any project like this will involve variation by some combination of preference and environmental necessity.

I recommend following the official guide, to which I defaulted throughout the process, from which I deviated slightly as follows. This is not intended as a replacement for or even supplement to the official guide so much as a place of refuge in case someone runs into trouble.

Identity

Your unique identity within the Urbit namespace is basically a phonetic IP address and is called a planet (with stars and galaxies up the network hierarchy). If you don’t have any connections in the Urbit community, you can start by booting up a comet and then asking around for a planet, or you can just buy one. I’ve skimmed at least a dozen websites that sell them either in USD or ETH, and after recent cost reductions in the way these are generated it’s not uncommon to see them sold for $10 – $30. This is a great way to support someone you like – or you can pay (currently) much less by buying a planet on Azimuth for 0.002 ETH which for me 5 days ago amounted to less than $3 with gas fees.

Server Basics

I’ve had good luck with another VPS provider over the years, but for this project I opted for a vanilla Ubuntu 20.04 LTS server through AWS Lightsail for $5/month – mainly because the first 3 months are free.

Urbit these days is accessed both as a command-line shell called dojo and broadcast through a web interface called landscape. The official documentation by default advises you to run sudo setcap ‘cap_net_bind_service=+ep’ ~/urbit/urbit so that Urbit is prioritized access to the HTTP port 80 as the default web server though I ended up electing to reverse this via sudo setcap -r ~/urbit/urbit so that it runs on 8080 instead. I then ran the former command to the path of my nginx binary instead, and configured Nginx as a proxy server.

Note that Urbit guides which point you in this direction tend to be older, and it’s likely that future versions of the urbit binary will likely require flags like –http-port for such configurations to work consistently.

I’m a hacker, and I learn things my own way; despite the fact that I’ve run various headless web servers, I’ve never really had to think about port access before and I got a better understanding of the basics while trying to diagnose what I initially suspected were improperly configured SSL certificates.

I eventually discovered AWS has their own proprietary network firewall, accessed through the web console and independent of the firewalls running inside the actual Linux system like ufw/iptables. HTTPS (port 443) is disabled by default.

I suppose this might not have been the case had I opted for a VM that had a webserver pre-installed, but the choice was between installing things myself on Ubuntu or loading the RHEL-based “Amazon Linux,” about which I had at least a couple reservations.

For setting up HTTPS with LetsEncrypt, Amazon recommends you use the snap version on their servers rather than python3-certbot-nginx, that you do it while the web server’s systemd service is stopped, and that you then go in and incorporate the public keys into your server configuration manually. Note that Amazon’s exact instructions vary depending on which type of distribution package you have.

I don’t think this is necessary, but in my case I used their method to validate my certificates and copied over pieces from a configuration I had generated with python3-certbot-nginx previously.

Media Management

To facilitate uploading media, Landscape is designed to integrate with a file service Amazon created called S3. The official guide walks you through the process of setting up a S3 “bucket” wherein they recommend purchasing a package from DigitalOcean. Amazon of course offers the service as well, but their documentation suggests their API no longer validates V2 Signatures, which Urbit requires.

Fortunately, the Urbit team accurately outlines how to install and configure MinIO for a self-hosted solution. One thing they don’t spell out for you (unless I missed it) is that you’re creating a docker container for MinIO that is saved and can be run after future reboots via docker start minio-urbit

I deviated from their instructions only in that I used Nginx instead of using Caddy. I had made this decision only while misdiagnosing the problems I mentioned above, so I do not think it should be necessary.

Nginx Configuration

This site was very helpful in translating the recommended setup for Caddy into an identically functional Nginx configuration. The MinIO configuration involves the creation of two proxy servers, into which I integrated a third for Landscape.

One advantage to doing this with Caddy is that it would have automated the validation and installation of the SSL certificates. In the case of other web servers, you just have to set everything up for HTTP and use a version of Certbot that will update the config for you. In the configuration template below, Urbit runs on 8080 (HTTP), MinIO runs on 9000 and 9001, and Nginx receives all three and sends them out on 80 (HTTP) and 443 (HTTPS).

/etc/nginx/sites-enabled/main

server {
        server_name urbit.yourdomain.org;
        location / {
                proxy_set_header Host $host;
                proxy_set_header Connection '';
                proxy_http_version 1.1;
                proxy_pass http://127.0.0.1:8080;
                chunked_transfer_encoding off;
                proxy_buffering off;
                proxy_cache off;
                proxy_redirect default;
                proxy_set_header Forwarded for=$remote_addr;
        }

    listen 80; # managed by Certbot

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/yourpath/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/yourpath/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 

    # Redirect non-https traffic to https
    if ($scheme != "https") {
        return 301 https://$host$request_uri;
    } # managed by Certbot

}

/etc/nginx/sites-enabled/bucket

server {
    # MinIO console
    server_name console.s3.yourdomain.org;
    ignore_invalid_headers off;
    client_max_body_size 0;
    proxy_buffering off;

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_connect_timeout 300;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        chunked_transfer_encoding off;
        proxy_pass http://localhost:9001;
   }

    listen 80; 

    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/yourpath/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/yourpath/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 

}

server {
    # MinIO server
    server_name s3.yourdomain.org;
    ignore_invalid_headers off;
    client_max_body_size 0;
    proxy_buffering off;

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_connect_timeout 300;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        chunked_transfer_encoding off;
        proxy_pass http://localhost:9000;
   }

    listen 80; 

    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/yourpath/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/yourpath/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 

}

server {
    # MinIO server
    server_name media.s3.yourdomain.org;
    ignore_invalid_headers off;
    client_max_body_size 0;
    proxy_buffering off;

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_connect_timeout 300;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        chunked_transfer_encoding off;
        proxy_pass http://localhost:9000;
   }

    listen 80; 

    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/yourpath/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/yourpath/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 

}

Shell Scripts

In order to run Urbit as a background process but also be able to access its shell in the foreground as needed, I downloaded and compiled abduco which is very easy and should have no awkward dependencies. This serves the same purpose as tmux or screen as Urbit recommends except that these latter two are terminal multiplexers and abduco is only a session manager.

I then incorporated abduco into 3 shell scripts in my user’s ~/bin directory (I can’t recall if this is part of $PATH by default) and in which its function should be clarified below. In the words of Dr. Emmett L. Brown, “please excuse the crudity of this model, I didn’t have time to build it to scale.”

~/bin/urbit-boot

#!/bin/sh
# If urbit isn’t running, launch an abduco session named “urbit,” in which the command urbit
# is run, and for which Ctrl+Q is bound to hide the session. 

pidof urbit && {

printf '\n%s\n\n' "An instance of urbit is already running."; exit 1;

}

printf '\n%s\n%s\n\n' \
"Launching new detachable Urbit instance via abduco."  \
"Press Ctrl+Q to return to shell. Restore with urbit-restore." 

abduco -e ^q -c urbit $HOME/urbit/urbit -p 34543 $HOME/urbit/sampel-palnet

~/bin/urbit-restore

#!/bin/sh
# Find the abduco process with the session name urbit and
# re-attach it to the current command-line. If urbit isn’t running, return an error.

printf '\n%s\n%s\n\n'  \
"Restoring existing Urbit instance via abduco."   \
"Press Ctrl+Q to return to shell. Restore  \
with urbit-restore."

abduco -e ^q -a urbit || {

pidof urbit && { printf '\n%s\n\n' "Urbit is already running without an attachable session." ; exit 0; }
#must return successful exit code, 0; error means urbit needs to be run!

printf '\n%s\n\n' "Urbit is not running."

exit 1

}

Finally, this third script really could just be a function:

~/bin/urbit

#!/bin/sh
# If urbit-restore returns an error, make sure urbit really isn’t
# running, and then run urbit-boot.
urbit-restore || pidof urbit || urbit-boot 

Creating a Systemd Service

Finally, a stable web server should be able to survive reboots without manual intervention. In other words we need to automate the launching of the MinIO docker container as well as the urbit server. There are different ways of doing this, but here’s what I did.

/opt/root-launches-users-urbit

#!/bin/sh
# Root automatically logs user in, runs usual start script as the
# user, then starts docker

#launch urbit for user as reattachable abduco session
runuser -l  myuser -c "/home/myuser/bin/urbit-boot"  >/dev/null 2>&1 &

#launch minio for connected media bucket
docker start minio-urbit

/etc/systemd/system/urbit-daemon.service

[Unit]
Description=root starts urbit and media bucket for user at system boot

[Service]
ExecStart=/opt/root-launches-users-urbit

[Install]
WantedBy=multi-user.target