metz·log

Seafile on Nginx with uWSGI

·

I've been using Seafile as my personal cloud storage for quite some time now and it's working really really well. Since I've departed quite a bit from the standard setup instructions for the web frontend (Seahub) I'm going to document the setup a tiny little bit. For complete Seafile setup instructions that include all components and not only the web frontend please refer to the Seafile Server Manual.

Running Seahub as a WSGI application

The standard setup instructions recommend to run Seahub behind Nginx as a FastCGI application. However internally Seahub is actually a WSGI application which is the standard way to communicate with Python based web-applications. Since I'm using uWSGI for managing and running several other services already I opted to skip the FastCGI wrapper in front of Seahub and let uWSGI start the web application with the correct environment instead. This also means seahub.sh is not used anymore.

The configuration I use for uWSGI and Seafile Server 3.1.6 looks like this:

/etc/uwsgi/apps-available/seafile.ini

[uwsgi]
plugins = python
uid = seafile
gid = seafile

topdir = /srv/seafile
env = CCNET_CONF_DIR=%(topdir)/ccnet
env = SEAFILE_CONF_DIR=%(topdir)/seafile-data
env = SEAHUB_LOG_DIR=%(topdir)/logs

installpath = /srv/seafile/seafile-server-latest
env = PYTHONPATH=%(installpath)/seahub:%(installpath)/seahub/thirdpart: \
  %(installpath)/seafile/lib64/python2.6/site-packages
env = DJANGO_SETTINGS_MODULE=seahub.settings

module = seahub.wsgi:application

Most environment variables set here are normally set in seahub.sh before starting the Python-based HTTP- or FastCGI-Server. The job of configuring the environment is now done by uWSGI and the above configuration file instead. For Python 2.7 the PYTHONPATH contents obviously need to be adapted.

The only exception in this configuration is DJANGO_SETTINGS_MODULE which is normally set inside seahub/wsgi.py early on. Unfortunately this did not seem to work when running the application from uWSGI (probably since the app cannot permanently modify the environment) so I had to set this inside the uWSGI configuration file.

Configuring Nginx

For Nginx the configuration is almost identical to the Seafile FastCGI setup documented in the manual. The only change is that the root location element is a lot simpler and uses uwsgi_pass instead of fastcgi_pass.

/etc/nginx/sites-available/seafile.conf

server {
    listen 80;
    server_name seafile.example.com;
    return 301 https://$server_name$request_uri;
}
server {
    listen 443 ssl;
    server_name seafile.example.com;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/var/run/uwsgi/app/seafile/socket;
    }

    location /seafhttp {
        rewrite ^/seafhttp(.*)$ $1 break;
        proxy_pass http://127.0.0.1:8082;
        client_max_body_size 0;
    }

    location /media {
        root /srv/seafile/seafile-server-latest/seahub;
    }
}

Running (almost) anything on Nginx with uWSGI

·

This is a sort of follow-up post to Running Trac on Nginx with Phusion Passenger. As my hosting needs have slightly changed lately I now have no immediate need for running Ruby, Rails or Rack applications. I'm lately using more and more web applications but they are based on different technologies. The current list looks like:

From the list above, Phusion Passenger currently only supports Trac. Also it doesn't seem to work with the Firefox Sync Server as its standard installation recommends virtualenv and Passenger always uses the system-wide Python installation unless you want to apply ugly hacks (loading another interpreter from a running Python script).

So overall Phusion Passenger did not look like a good match for my needs anymore and I started looking for something else that could handle as many technologies as possible without me needing to setup a different application server for each technology (and learning a different configuration syntax for each of them).

Enter uWSGI

It turns out that there's already something that does handle most (if not all) of the above mentioned languages or frameworks and it's called uWSGI. Despite its name, uWSGI supports far more than running Python-based UWSGI applications thanks to its plugin system and a truckload of plugins that are part of the standard distribution. This also includes Rack-based applications (probably includes Rails as well), PHP and even plain old CGI.

Unfortunately uWSGI is not yet available in Debian Squeeze (stable) but it's part of Debian Wheezy (testing). Because Wheezy is currently in feature freeze and has proven to be "stable enough" on all my Linux boxes I've decided to use Debian Wheezy for my new root-server.

Installing uWSGI on Debian Wheezy

Installing uWSGI is just a matter of apt-get install uwsgi-core and then add any of uwsgi-plugin-foo packages to the mix. I started with uwsgi-plugin-python and uwsgi-plugin-cgi.

For some reason Debian does not package the PHP plugin for uWSGI, fortunately building it manually is not that much work:

  • Install packages libphp5-embed and php5-dev
  • Fetch uwsgi package sources using apt-get source uwsgi
  • Copy debian/buildconf/uwsgi-plugin.ini.in to debian/buildconf/uwsgi-plugin.ini and replace @@curdir@@ with the absolute path to the source directory
  • Execute python uwsgiconfig.py --plugin plugins/php debian/buildconf/uwsgi-plugin.ini to build the plugin
  • The resulting plugin_php.so can now be copied to /usr/lib/uwsgi/plugins/

Please note that every time libphp5-embed received an update so far I also had to rebuild the plugin. I don't know exactly why that's the case but I guess it's a trick to make me submit a patch to the Debian uWSGI packager(s) ;)

Configuring uWSGI and Nginx

In the following I'll show a few configuration examples how I got different applications and languages working with uWSGI and Nginx.

Because all this is on Debian Wheezy every uWSGI app creates a socket at /var/run/uwsgi/app/APPNAME/socket. For more information about the default configuration of an application just check /usr/share/uwsgi/conf/default.ini and uWSGI Configuration Options.

In my examples all uWSGI apps run as their own user instead of www-data. This is of course not needed but should be a little bit more secure. For even more separation it would probably make sense to add chrooting and setting POSIX Capabilites as documented in the Securing uWSGI section of uWSGI documentation but I'll omit that to keep the examples small.

Cgit on Nginx with uWSGI

Getting a CGI application like cgit working is quite easy, at least if there's only one CGI binary to execute.

/etc/uwsgi/apps-available/cgit.ini

[uwsgi]
plugins = cgi
chown-socket = www-data:www-data
uid = cgit
gid = cgit
processes = 1
threads = 8
cgi = /usr/local/lib/cgi-bin/cgit.cgi

For Nginx it's just a matter of adding a new virtual host and pointing it to the socket created by the above configuration:

/etc/nginx/sites-available/com.example.git.conf

server {
    root /srv/www/com.example.git;
    server_name git.example.com;
    location / {
        try_files $uri @cgit;
    }
    location @cgit {
        include uwsgi_params;
        uwsgi_modifier1 9;
        uwsgi_pass unix:/var/run/uwsgi/app/cgit/socket;
    }
}

Trac on Nginx with uWSGI

Setting up Trac is very similar, except that defining the entry point of a Python-based WSGI application works a bit different:

/etc/uwsgi/apps-available/trac.ini

[uwsgi]
plugins = python
chown-socket = www-data:www-data
uid = trac
gid = trac
env = TRAC_ENV=/srv/trac/projectname
module = trac.web.main:dispatch_request

Adding a virtual host to Nginx is also only a few lines:

/etc/nginx/sites-available/com.example.trac.conf

server {
    root /srv/www/com.example.trac;
    server_name trac.example.com;
    location / {
        include uwsgi_params;
        uwsgi_pass unix:/var/run/uwsgi/app/trac/socket;
    }
}

Piwik on Nginx with uWSGI

Getting Piwik running is easy in terms of uWSGI and shows little changes except for the plugins key:

/etc/uwsgi/apps-available/trac.ini

[uwsgi]
plugins = php
chown-socket = www-data:www-data
uid = piwik
gid = piwik
cheaper = 1
processes = 4

Now the Nginx configuration is a bit more elaborate. Parts of it are based on François Lefèvre's post about Piwik Hardening with Nginx and PHP-FPM and perusio's piwik-nginx config example at GitHub.

/etc/nginx/sites-available/com.example.trac.conf

server {
    listen 80;
    root /srv/www/com.example.piwik;
    server_name piwik.example.com;
    index piwik.php;
    # For administrative access
    location = /index.php {
        include uwsgi_params;
        uwsgi_modifier1 14;
        uwsgi_pass unix:/var/run/uwsgi/app/piwik/socket;
        allow 127.0.0.1; # only via ssh tunnel
        deny all;
    }
    # For public access
    location = /piwik.php {
        include uwsgi_params;
        uwsgi_modifier1 14;
        uwsgi_pass unix:/var/run/uwsgi/app/piwik/socket;
    }
    # Any other attempt to access PHP files is forbidden
    location ~* ^.+\.php$ {
        return 403;
    }
    # Redirect to the root if attempting to access a txt file.
    location ~* (?:DESIGN|(?:gpl|README|LICENSE)[^.]*|LEGALNOTICE)(?:\.txt)*$ {
        return 302 /;
    }
    # Disallow access to several helper files.
    location ~* \.(?:bat|html?|git|ini|sh|svn[^.]*|txt|tpl|xml)$ {
        return 404;
    }
    # Disallow access to directories
    location ~ ^/(config|core|lang|misc|tmp)/ {
        deny all;
    }
}

Good bye Apache and mod-fcgid

uWSGI is a real breeze to configure. Combining it with Nginx gave me an IMHO pretty clean solution for separating web applications from eachother and having a single point for configuring apps regardless of what technology they use.

Especially the configuration part got me sold on uWSGI after I looked back at all the hoops I had to jump through in the past with apache fcgid/fastcgi modules, suexec, php-fpm and numerous wrapper-scripts to get fastcgi and suexec to play nicely together.

Finally I also have gained easier control over the number of spawned processes, something which wasn't so straightforward with mod-fcgid and php5-cgi.

Using nullmailer with STARTTLS and SMTP-Auth

·

So far I've been using Nullmailer only to relay mail from LAN machines to SMTP servers that accept anything from the internal network, a very easy method to forward system messages from various internal boxes without the need to administrate a full-blown MTA. For my home box however the relay server is external and so it needs both STARTTLS and SMTP-AUTH on the submission port to accept mail from my dynamic IP. Until now nullmailer did not support STARTTLS or at least I could not find any documentation on it.

Getting things working

With nullmailer 1.10 this has finally changed, the documentation that one usually looks at (homepage, manpage, /usr/share/doc/nullmailer/) however is still lacking so I decided to write down how I got things working.

The key to enable STARTTLS for a remote server in nullmailer is simply to add --starttls to remote configuration which can usually be found in /etc/nullmailer/remotes:

mail.example.com smtp --port=587 --starttls --user=mailalot --pass=LetMeIn

And that's actually all there is to it :)

Why does this work

After some more digging it turned out that the --option parts in the above configuration file are simply forwarded to /usr/lib/nullmailer/smtp which is the binary that handles the actual SMTP dialog. And voila, this binary even has a --help switch which will present you the following:

usage: smtp [flags] remote-address < mail-file
Send an email message via SMTP
  -p, --port=INT            Set the port number on the remote host to connect to
      --user=VALUE          Set the user name for authentication
      --pass=VALUE          Set the password for authentication
  -d, --daemon              use syslog exclusively
  -s, --syslog              use syslog additionally
      --auth-login          Use AUTH LOGIN instead of auto-detecting in SMTP
      --ssl                 Connect using SSL (on an alternate port by default)
      --starttls            Use STARTTLS command
      --x509certfile=VALUE  Client certificate file
      --x509cafile=VALUE    Certificate authority trust file
                            (Defaults to /etc/ssl/certs/ca-certificates.crt)
      --x509crlfile=VALUE   Certificate revocation list file
      --x509fmtder          X.509 files are in DER format
                            (Defaults to PEM format)
      --insecure            Don`t abort if server certificate fails validation

  -h, --help                Display this help and exit

So after a few minutes of wandering around my box, looking at files and package contents I could finally find out how to replace my local Postfix install (a bit overkill for a home machine) with the more lightweight Nullmailer.

Running Trac on Nginx with Phusion Passenger

·

I'm currently investigating ways to replace Apache httpd with Nginx on srcbox.net. One of the services that runs on the webserver is Trac for hosting a wiki-based website and bug tracking for software projects. Because I'm already using Phusion Passenger for hosting Ruby on Rails projects, I decided to also give it a try to host WSGI applications together with Nginx.

Please note that this post is quite similar to the one written by Graham Edgecombe about the same subject. I found a few things missing and notable so I decided to do my own little writeup on what I had to do to get things working.

Trac Setup

As the actual Trac setup is already present on my server and I just wanted to replicate a similar setup in my testing VM, I setup Trac like this:

  • Installed package trac, I used version 0.12.x which is available via Debian Backports
  • Added a system user that owns all trac project files and is used to run the WSGI webserver processes: adduser --system --home /var/lib/trac --group --disabled-password --disabled-login trac
  • Initialized a trac environment below trac home: trac-admin /var/lib/trac/project initenv
  • Deployed the webserver files using: trac-admin /var/lib/trac/project deploy /var/www/trac/
  • Added symlinks to make Phusion Passenger accept Trac as a WSGI application: ln -s /var/www/trac/htdocs /var/www/trac/public ln -s /var/www/trac/cgi-bin/trac.wsgi /var/www/trac/passenger_wsgi.py

Installing Nginx

Nginx installation is quite a bit of work if you're used to a single aptitude install package, all features/modules have to be enabled/added at build-time which means rebuilding Nginx if you want to use Phusion Passenger. Because I plan to move authentication to a central place soon I also added Nginx HTTP Auth PAM to the mix. Furthermore I'm also tinkering with the idea to give owncloud a spin so WebDAV support is probably helpful too.

The installation basically went like this (rough steps, this is not a place for step-by-step tutorials):

  • Downloaded latest stable Nginx release
  • Downloaded latest ngx_http_auth_pam_module release
  • Unpacked both in a convenient place
  • Installed Phusion Passenger via rubygems (I used ruby and gem from ruby1.9.1 in Debian Squeeze)
  • Ran passenger-install-nginx-module
  • Chose 2. No: I want to customize my Nginx installation.
  • Told the installer where to find unpacked Nginx sources
  • Added additional configure arguments. For Auth PAM and optionally needed WebDAV support I used: --add-module=/path/to/unpacked/ngx_http_auth_pam_module --with-http_dav_module
  • Grabbed an Espresso until the build was finished (didn't really take long)

On Debian it's probably helpful to install a fake webserver package to satisfy dependencies for other packages that depend on http-server. I used equivs to generate myself an nginx-dummy package.

Configuring Nginx

For starting up Nginx I simply grabbed the init-script from the Debian source package of nginx, , put it into /etc/init.d/, adapted the paths to my Nginx install in /opt/nginx and installed it in the default runlevels using insserv nginx.

Adding a virtual host to Nginx that serves Ruby on Rails or a WSGI application is usually very easy, for Trac however a tiny bit more care had to be taken...

Pitfall 1: HTTP Auth

With Trac there's one catch: HTTP Authentication. It took me quite some time to find out why HTTP-Auth worked fine according to the Nginx access.log but Trac still thought I wasn't logged in: Nginx needs to forward the user name to the WSGI application. For Phusion Passenger this can be done using the following line: passenger_set_cgi_param REMOTE_USER $remote_user;

Pitfall 2: Location Blocks

Nginx uses location blocks for different behavior depending on the url. For Trac the /login path should trigger HTTP authentication. Together with Phusion Passenger all I got after successful login however was a simple "404 Not Found" page.

It turned out that for location blocks one has to enable Phusion Passenger again, a prior passenger_enabled on; line in the parent block does not apply to the inner location block.

Final Nginx Config

The complete host configuration for my test setup that contains all needed things to avoid the above pitfalls looks like this:

server {
    listen                  80;
    server_name             trac.domain.lan;
    root                    /var/www/trac/public;
    passenger_enabled       on;
    # Forward HTTP-Auth user to WSGI
    passenger_set_cgi_param REMOTE_USER $remote_user;

    location /login {
        auth_pam                "Trac on domain.lan - Restricted Access";
        # Use the same PAM service as dovecot for now
        auth_pam_service_name   "dovecot";
        # Reenable Passenger to get a redirect after login instead of a 404
        passenger_enabled       on;
        # Forward HTTP-Auth user to WSGI
        passenger_set_cgi_param REMOTE_USER $remote_user;
    }
}

What next?

This setup is probably still far from complete. Most notable things that I did not setup or test yet include:

  • Paths for a manual Nginx install do not obey the FHS at all, logs and temporary files end up on the wrong partition which is a bad idea security wise.
  • Log rotation is missing. I can probably grab parts from the Debian Nginx package again.
  • Serve multiple projects/environments with the above webserver setup
  • Replace the rather ugly HTTP Authentication with Trac Account Manager plugin
  • Use TLS and enforce login to happen encrypted only

Of course Trac is by far not the only web-application, I'm probably going to document a few other things I have installed as part of my new Nginx test installation :)