One of my favorite aspects of the IndieWeb community is that when you get things "right" with your website, you often get a bunch of fun interoperability with other IndieWeb-compatible websites "for free". For example, the Micropub standard lets you use lots of different clients to post to your own site, and the Webmention standard lets sites notify one another of things like comments, event RSVPs, etc.
Fundamental to having these technologies work well together is microformats2 (mf2), a lightweight way of marking up "structural information" in HTML so that a machine can make (some) sense of the information, such as the name, url, and photo of the author, hints on the important pieces of content in a page, etc.
Getting these things "right" on my own website led me to look for a "Reader" that would make use of the mf2 data and attempt to display it in a meaningful way.
One of the popular readers I saw talked about in the #IndieWeb chat was Woodwind. It was easy to get started by logging in with my own website and then subscribing to my own site to get all my h-feeds, h-entrys, h-cards, etc. in a row. Recently, the hosted version of Woodwind at https://woodwind.xyz/ was down for a few days, so I set out to host my own.
Initial Setup
Thankfully, Woodwind is on GitHub and the Installation instructions are pretty good for getting started. Since I already had a server with the expected dependencies (Python3, PostgreSQL, and Redis), I was able to get a test site up and running in a few steps:
- clone the git repo
git clone https://github.com/kylewm/woodwind.git
cd woodwind
- create Python3 virtualenv and activate it
virtualenv --python=/usr/bin/python3 venv
source venv/bin/activate
- install the required Python libraries with pip
sudo apt-get install python3-dev
pip install -r requirements.txt
as the postgres user, create the woodwind database and the database user that would access it
$ sudo -u postgres createdb woodwind
$ sudo -u postgres psql woodwind
woodwind=# create user woodwind_user with password '...'
- copy woodwind.cfg.template to woodwind.cfg and edit it up
SECRET_KEY = '...'
SERVER_NAME = 'woodwind.yourdomain.com'
SQLALCHEMY_DATABASE_URI = 'postgres://woodwind_user:DB_PASSWORD@localhost/woodwind'
- run the init_db.py script
python init_db.py
- at this point I discovered a typo in
woodwind/views.py
that was throwing errors - a missing parenthesis. once fixed, this ran fine. - I've created a pull request for this, so kylewm can merge it back in eventually.
- finally, use
uwsgi
to run the demo version
uwsgi woodwind-dev.ini
- visit localhost:3000 in my browser and I could see that woodwind was running!
This had me off to a very good start, but I wanted to be able to visit my copy of Woodwind from anywhere using a public domain name, protect my activity from eavesdroppers on the network with HTTPS, and have Woodwind up and running reliably across server crashes, reboots, etc.
Setting up Woodwind with uwsgi, Upstart, and nginx
Woodwind is an application written in Python. uwsgi is an application server that can run that code on demand, efficiently. It is possible to run uwsgi by hand as we did above, but I wanted the service to be started and managed automatically by the operating system.
I run an Ubuntu server with the upstart process manager. So, I created an upstart configuration for Woodwind at /etc/init/woodwind.conf:
description "woodwind uwsgi instance"
start on runlevel []
stop on runlevel []
respawn
setuid woodwind_user
setgid woodwind_user
chdir /home/woodwind_user/woodwind
env LC_ALL=C.UTF-8
export LC_ALL
env LANG=C.UTF-8
export LANG
script
. venv/bin/activate
uwsgi --ini woodwind.ini
end script
With this, the uwsgi server should start up on boot to serve Woodwind, and I can now manage woodwind from the command line. For example:
$ sudo start woodwind
woodwind start/running, process 14104
$ status woodwind
woodwind start/running, process 14104
$ sudo restart woodwind
woodwind start/running, process 14246
$ sudo stop woodwind
woodwind stop/waiting
Since I wanted to use HTTPS to protect my activity on Woodwind from network eavesdropping, I used Let's Encrypt and their certbot tool to create an SSL certificate for my domain. The steps are:
- Create a DNS entry for woodwind.yourdomain.com to point to the public IP address of my server. This may take some time to propagate and certbot won't work until it has taken effect.
- Install certbot
- Stop nginx from running, temporarily.
- Use certbot to issue a
./certbot-auto certonly --standalone \
--standalone-supported-challenges http-01 \
-d woodwind.yourdomain.com
This resulted in an SSL certificate and key pair that I could use to encrypt traffic to this domain.
Next up, I need something to actually handle the HTTPS requests and pass them along to uwsgi. I used nginx for this because I was already using it on this server. In my nginx config directory, I created a woodwind.conf file:
upstream woodwind {
server unix:/tmp/woodwind.sock;
}
upstream woodwind_wss {
server localhost:8077;
}
server {
listen *:80;
server_name woodwind.maktro.net;
server_tokens off;
ssl on;
ssl_certificate /etc/letsencrypt/live/woodwind.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/woodwind.yourdomain.com/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_dhparam /etc/ssl/certs/dhparam.pem;
root /home/woodwind_user/woodwind;
access_log /var/log/nginx/woodwind_access.log;
error_log /var/log/nginx/woodwind_error.log;
location /_updates {
uwsgi_pass woodwind_wss;
}
location / {
try_files /woodwind/static/$uri /frontend/$uri @woodwind;
}
location @woodwind {
uwsgi_pass woodwind;
include uwsgi_params;
uwsgi_buffering off;
}
}
This nginx configuration has some things worth noting:
After all this setup, I restarted nginx and was able to visit Woodwind in my browser!
I am happy with my setup so far. I am not quite sure yet if I did the WebSockets configuration correctly, but in general things seem to be working alright. I hope this information is useful to someone down the road, even if it is just future me.