Installing Discourse on Ubuntu Trusty 32-bit
Last time I evaluated comment systems suitable for a static blog such as this and came with a conclusion that Discourse is the one and only solution (short of rolling my own).
However, this requires setting up a Discourse instance.
I naturally want to install it on my home server that does my multiprovider Internet routing, powers the whole apartment networking infrastructure (DHCP, local DNS, those sorts of things), hosts a few playground projects, and is overall a not-so-shabby machine that I’m not very keen on upgrading.
What’s the problem, anyway?
The quirk is that this so-called server is what used to be my previous desktop. Namely, a Pentium 4, family 15, model 2, stepping 9, which means it’s a Northwood, the last Pentium 4 family that did not support 64-bit instructions.
Now, Discourse is primarily distributed as a Docker container. And Docker only runs on 64-bit machines.
So the Docker installation scenario is out for me. No problem, I hear there also is a manual installation guide.
Hardware requirements
The guide recommends 2G RAM, 2G swap, and two processor cores. It lists the minimum as 1G RAM, 3G swap and a single core. I have 1G RAM, 3G swap and a single hyperthreaded core, and I don’t estimate a very large load, so expect it to cope.
Dependencies
So I take my fully up-to-date Ubuntu 14.04 server. OpenSSH: check.
Mail server: check. PostgreSQL: check. Git: check. Redis: aptitude
install redis-server
, check. Nginx: check. Ruby 2.0? aptitude
install ruby2.0
, check.
Now what’s this, RVM? Ruby Version Manager? Downloads and builds any version of Ruby right in your unprivileged user’s home directory? Oh no you don’t! I have a properly deb-packaged system-wide installations of Ruby 1.9.1 and Ruby 2.0.0, with a selection of properly deb-packaged Ruby Gems, and I intend to use those as much as possible.
So let’s skip that. Instead, I create a discourse
user account, add
it to the sudo
group (for the duration of installation only), create
a /home/discourse/bin
directory and symlink the binaries from the
ruby2.0
package into there, under their unversioned names:
dpkg -L ruby2.0|sed -nE 's:^(/usr/bin/(.*)2\.0)$:ln -s \1 /home/discourse/\2:p'
This way, logging in as discourse
and typing ruby --version
, I get:
ruby 2.0.0p384 (2014-01-12) [i386-linux-gnu]
Gems
Now, the guide says gem install bundler
. Since bundler is packaged
by Ubuntu, I want to use that instead. sudo aptitude install
bundler
.
Next, fork and clone the repository and check out a stable release.
install -m 755 -o discourse -g discourse -d /var/www-discourse
cd /var/www-discourse
git clone https://github.com/yurikhan/discourse.git .
git checkout latest-release
The next step seems to be installing necessary gems. This is a bit of a problem because the default suggested way is by using Bundler, which will attempt to download and install any necessary gems that I don’t already have installed. This will use the rubygems.org index rather than the Ubuntu package repository, and that’s a concern for me. I’d rather prefer if applications used system-provided libraries, not bring everything they need with them.
At the same time, I don’t want to resolve all the dependencies by myself. So let it do its thing, for now.
sudo aptitude install ruby2.0-dev postgresql-server-dev-9.3 libpq-dev
bundle install --deployment --without development:test
It tells me my bundler is out-of-date and if I could please update it. No I won’t. From what I understand it’s just a (secondary) package manager, and if my primary package management system says it strikes a good balance between stable and featureful, then so be it.
However, it goes on downloading and installing. In the process, it
tells me that a gem called nokogiri
comes with its custom version of
libxml2
and libxslt
because libxml2 2.9.0 is known to be broken.
Well, duh! If it’s broken, declare a package dependency on libxml2
(<< 2.9.0)
and be done with it. Oh shit, you are a ruby gem and
cannot declare deb package dependencies. Tough luck. Well, I’ll deal
with you later.
All in all, gem installation went without a hitch. (Not really; guess
how I knew to install those three *-dev
packages above.) As far as I
can tell, gems were installed right into the project directory, under
vendor/bundle
, whose final size is some 280M. In the /var
hierarchy, which is supposed to contain only files that are at least
sometimes modified. Totally inappropriate.
Configuration
This step is pretty straightforward.
Nginx configuration
The guide says copy nginx.global.conf
and nginx.sample.conf
to
/etc/nginx/conf.d
as local-server.conf
and discourse.conf
,
respectively. On Ubuntu, virtual server configs are supposed to be in
/etc/nginx/sites-available
with a symlink from
/etc/nginx/sites-enabled
, so nginx.sample.conf
goes there. And I
don’t initially see the need for tweaking
server_names_hash_bucket_size
which is all that local-server.conf
does, so let’s try without that first.
On reload attempt, nginx complains about the cache directory which is
set in discourse
as /var/cache/nginx
. Let’s create that.
While we are at it, let’s also configure logging to a separate directory.
access_log /var/log/nginx/discourse/access.log;
error_log /var/log/nginx/discourse/error.log;
sudo install -m 755 -o www-data -g adm -D /var/cache/nginx /var/log/nginx/discourse
Managing services
The guide suggests installing a gem called Bluepill and running
Discourse using the config/discourse.pill
file.
Bluepill is in fact a service monitoring daemon written in Ruby. This means we can do without it, as Ubuntu already has two service managers: SysV init and Upstart.
As far as I can tell, we need two services running: sidekiq
and
thin
. Let’s start with the latter as it is simpler.
Actually, I already use thin
for a Redmine instance, so I am a
little familiar with its configuration. Basically, you put your thin
configs into /etc/thin1.9.1
or /etc/thin2.0
, and the
Ubuntu-packaged /etc/init.d/thin
script takes care of the rest.
The config goes roughly like this:
---
pid: /var/www-discourse/tmp/pids/thin.pid
wait: 30
timeout: 30
log: /var/www-discourse/log/thin.log
max_conns: 1024
require: []
environment: production
max_persistent_conns: 512
no-epoll: true
servers: 4
daemonize: true
socket: /var/www-discourse/tmp/sockets/thin.sock
chdir: /var/www-discourse
user: discourse
group: discourse
For sidekiq
, there are Upstart configs in the
upstream repository. I adapt one for my Discourse instance:
# /etc/init/discourse-sidekiq.conf - Sidekiq config
#
# Adapted from https://github.com/mperham/sidekiq/blob/master/examples/upstart/manage-one/sidekiq.conf
description "Sidekiq Background Worker"
start on runlevel [2345]
stop on runlevel [06]
# change to match your deployment user
setuid discourse
setgid discourse
respawn
respawn limit 3 30
# TERM is sent by sidekiqctl when stopping sidekiq. Without declaring these as normal exit codes, it just respawns.
normal exit 0 TERM
script
exec /bin/bash <<EOT
export HOME=/home/discourse
export PATH=/home/discourse/bin:/usr/local/bin:/usr/bin:/bin
export PIDFILE=/var/www-discourse/tmp/pids/sidekiq-worker.pid
export RAILS_ENV=production
cd /var/www-discourse
bundle exec sidekiq -L /var/www-discourse/log/sidekiq.log
EOT
end script
Now, after restart thin && start sidekiq
, we should be up and running!
Except we’re not. thin
logs say some gems cannot be found, and the
stack trace mentions ruby1.9.1
.
I assume this is because the init script tries running
/usr/bin/ruby2.0 /usr/bin/thin start --all /etc/thin2.0
, but
/usr/bin/thin
has in its first line #!/usr/bin/env ruby
. Where
ruby
still refers to ruby1.9.1
because we’re running as root at
this point.
So let’s copy /usr/bin/thin
to /usr/bin/thin2.0
and replace ruby
with ruby2.0
in its shebang line, and also copy /etc/init.d/thin
to /etc/init.d/thin2.0
and modify both init scripts to only run
their corresponding thin
binaries.
This fixes thin
startup. Now the assets (js and css) are not served,
returning 403 Forbidden instead. That’s because nginx is running as
www-data
and the directory is owned by discourse
and some of its
files and subdirectories got 660 (resp. 770) permissions when I cloned
the repository. Moreover, when the application creates new files, they
also get that mask. So it seems best to add www-data
to the
discourse
group.
After that, it actually works. Lets me register an account, sends an activation email, and lets me enable Google and GitHub logins.