Index

Debian Server mit Lighttpd, Django und Fastcgi

Orginal-Artikel vom 07. April 2009: http://www.sio2.ch/blog/2/ Bearbeitet am 19.03.2011

Installation der benötigten Pakete

Um sicher zu sein, dass unser Server auf dem neustem Stand ist machen wir einen Update der Paketdatenbank und ein Update des Systems.

~/# apt-get update
~/# apt-get upgrade

Jetzt werden noch folgende Pakete benötigt:

~/# apt-get install apg build-essential lighttpd mysql-client mysql-server perl php5-cgi phpmyadmin python python-django python-flup python-mysqldb ssh openssh-server sudo vim-nox wget

Hier noch eine kleine Erläuterung der Pakete:

apg
generiert zufällige Passwörter
build-essential
wie der name schon sagt, enthält build-essential die essentielle Software und Bibliotheken zum compillieren
lighttpd
unser Webserver
mysql*
MySQL-Client und -Server
perl
perl Scriptingsprache
php5-cgi
php5-cgi wird - leider - von lighttpd benötigt um programme mit hilfe von fastcgi laufen zu lassen
phpmydmin
MySQL Webfrontend in PHP, bis jetzt habe ich keine bessere Alternative gefunden.
python
DIE Programmiersprache - nebst haskell, smalltalk-80, scala, vala, uvm.
python-django
unser Webframework
python-flup
wird von Django benötigt um django per fastcgi laufen zu lassen
python-mysqldb
Python-Modul um MySQL-Verbindungen herzustellen
ssh
SSH-Metapaket
openssh-server
OpenSSH-Server
sudo
um befehle als SuperUser auszuführen
vim-nox
mein Texteditor für den Server
wget
angenehme Software um Dateien aus dem Internet herunter zu laden

Lighttpd global konfigurieren

Nun editieren wir die Lighttpd-Konfigurationsdatei in /etc/lighttpd/lighttpd.conf und aktivieren ein paar Servermodule:

server.modules              = (
            "mod_access",
            "mod_alias",
            "mod_accesslog",
            "mod_compress",
            "mod_rewrite",
            "mod_redirect",
#           ...
)

Nun können wir ein paar weitere Module von Lighttpd aktivieren:

~/# lighttpd-enable-mod auth fastcgi proxy userdir

Jetzt ergänzen wir die Lighttpd-Konfigurationsdatei, damit sie auch für jeden Benutzer eine eigene Konfigurationsdatei in /etc/lighttpd/user-conf/ erlaubt. Folgende Zeile muss an den Schluss von /etc/lighttpd/lighttpd.conf hinzugefügt werden:

include_shell "/usr/share/lighttpd/include-user-conf.pl"

Als nächstes müssen wir das kleine Script - /usr/share/lighttpd/include-user-conf.pl - schreiben, welches die Konfigurationen der Benutzer in /etc/lighttpd/user-conf/ auflistet und sie in die Lighttpd-Konfiguration einbindet:

#!/usr/bin/perl -wl

use strict;
use File::Glob ':glob';

my $confdir = "/etc/lighttpd/";
my $enabled = "user-conf/*.conf";

chdir($confdir);
my @files = bsd_glob($enabled);

for my $file (@files)
{
        print "include \"$file\"";
}

Benutzer hinzufügen

Nun können wir unseren ersten Benutzer einfügen:

~/# adduser account

Dies fügt uns einen user "account" welchen wir als Vorlage benützen werden, um weitere Benutzer zu generieren.

Unser neu generierter Benutzer muss sich in der Gruppe www-data befinden, also fügen wir diesen der Gruppe zu:

~/# adduser account www-data

Datenbank für den Benutzer hinzufügen

Unser neuer Benutzer benötigt eine MySQL-Datenbank für seine Django-Projekte. Wir schreiben nun ein kleines Script, das uns für einen beliebigen Benutzer einen MySQL Benutzer generiert. Dafür benötigen wir ein kleines SQL-Template welches wir in /root/confs/create_user.sql abspeichern:

# Create the user
CREATE USER '$account'@'localhost' IDENTIFIED BY '$passwd';

# Grant usage to the user
GRANT USAGE ON * . * TO '$account'@'localhost' IDENTIFIED BY '$passwd';

# Grant all privileges to his databases
GRANT ALL PRIVILEGES ON `$account\_%` . * TO '$account'@'localhost';

Und nun das passende Bash-Script dazu um das Template anzupassen und den Benutzer hinzuzufügen:

#!/usr/bin/env bash

function usage {
    echo "Create a new mysql account"
    echo "Usage: ${0##*/} uid"
    if [ "$*" != "" ]; then echo " Error: $*"; fi
    exit 1
}

# the script must be run by root
if [ "$USER" != "root" ]; then
    echo "Must be run as root."
    exit 1
fi

# check parameters
if [ $# -ne 1 ]; then
    usage
fi

# get the account paramenter
account=$1

# generate a random password for the mysql user
passwrd=`apg -n 1 -c "0xa2t28n"`

tmpsql="/tmp/create_mysql_acc.sql"

# create temporary sql file
touch $tmpsql
sed \
	-e "s/\$account/`echo $account`/g" \
	-e "s/\$passwd/`echo $passwrd`/g" \
	"$HOME/confs/create_user.sql" >> $tmpsql

# create the sql user
echo "MySQL root password is required"
mysql -p < $tmpsql

# delete the temporary sql file
rm -f $tmpsql

# save settings in a file on his home directory
touch "/home/$account/mysql.settings"
echo "Username: $account" >> "/home/$account/mysql.settings"
echo "Password: $passwrd" >> "/home/$account/mysql.settings"

# make the file only readable by the user
chmod 700 "/home/$account/mysql.settings"
chown $account:$account "/home/$account/mysql.settings"

Dieses Script kann z.B. in /usr/local/sbin/create_mysql_acc gespeichert werden und so auch ausgeführt werden:

~/# create_mysql_acc account

Lighttpd für Django-Projekte konfigurieren

Nun kann Lighttpd für die Django-Projekte des erstellten Accounts konfiguriert werden. Auch hier werden wir ein Template verwenden, welches wir in /root/confs/lighttpd.conf abspeichern:

$HTTP["host"] =~ "^(www\.|)$domain$" {

  server.document-root = "/home/$account/django-projects/website/public_html"

  fastcgi.server = (
    "$proj.fcgi" => (
      "main" => (
        "socket" => "/tmp/$account/$proj.sock",
        "check-local" => "disable",
      )
    )
  )

  url.rewrite-once = (
    "^/(media)/.*" => "$0",
    "^(/.*)" => "/$proj.fcgi$1",
  )

}

Jetzt braucht es noch ein kleines Bash-Script, welches das Template ausliest, die Variablen ersetzt und dann die Konfiguration in /etc/lighttpd/user-conf/ ablegt:

#!/usr/bin/env bash

function usage {
    echo "Create the lighttpd proj conf and add it to /etc/lighttpd/user-conf/"
    echo "Usage: ${0##*/} uid domain proj"
    if [ "$*" != "" ]; then echo " Error: $*"; fi
    exit 1
}

function check_dir {
    if [ ! -d $1 ]; then
        echo "Creating directory $1 ..."
        mkdir $1
    fi
}

# the script must be run by root
if [ "$USER" != "root" ]; then
    echo "Must be run as root."
    exit 1
fi

# check parameters
if [ $# -ne 3 ]; then
    usage "uid or proj missing"
fi

account=$1; domain=$2; proj=$3;

# check if the user exists
if [ ! -e /home/$account ]; then
    usage "User $account does not exist at /home/$account"
fi

# check if the project already exists
if [ ! -e /home/$account/django-projects/$proj ]; then
    usage "Project $proj does not exist at /home/$account/django-projects"
fi

# check if the configuration file exists, else create it
DIR=/etc/lighttpd/user-conf
check_dir $DIR
if [ ! -e $DIR/$account.conf ]; then
    touch $DIR/$account.conf
fi

sed \
	-e "s/\$account/`echo $account`/g" \
	-e "s/\$domain/`echo $domain`/g" \
	-e "s/\$proj/`echo $proj`/g" \
	$HOME/confs/lighttpd.conf >> /etc/lighttpd/user-conf/$account.conf

Dieser Script kann z.B. in /usr/local/sbin/create_lighty_conf gespeichert werden und so auch ausgeführt werden:

~/# create_lighty_conf account example.com website

Django Projekt herstellen

Nun stellen wir einen Django-Projekt her. Dies ist dank dem django-admin ziemlich simpel und lässt sich auch mit Hilfe eines weiteren Scripts - /usr/local/sbin/startproj - ausführen:

#!/usr/bin/env bash

function usage {
    echo "Create a new django project"
    echo "Usage: ${0##*/} uid proj"
    if [ "$*" != "" ]; then echo " Error: $*"; fi
    exit 1
}

function check_dir {
    if [ ! -d $1 ]; then
        echo "Creating directory $1 ..."
        mkdir $1
    fi
}

# check parameters
if [ $# -ne 2 ]; then
    usage
fi

account=$1
project=$2

# check if the project already exists

DIR="/home/$account/django-projects"
if [ -e $DIR/$project ]; then
    usage "Project already exists at /home/$account/django-projects/$project"
fi

# save the current location
location=`pwd`

check_dir $DIR
cd $DIR/

# create the django project
django-admin startproject $project

# go back where we were before
cd $location

# create temporary sql to create the database
mysqlfile="/tmp/create_mysql_db.sql"
touch $mysqlfile
sed \
    -e "s/\$account/`echo $account`/g" \
    -e "s/\$project/`echo $project`/g" \
    "$HOME/confs/create_db.sql" >> $mysqlfile
echo "MySQL root password is required"
mysql -p < $mysqlfile
rm -f $mysqlfile

# create the fcgi file
DIR="/home/$account/django-projects/$project/public_html"
check_dir $DIR
fcgifile="$DIR/$project.fcgi"
touch $fcgifile
sed \
    -e "s/\$account/`echo $account`/g" \
    -e "s/\$project/`echo $project`/g" \
	"$HOME/confs/template.fcgi" >> $fcgifile

chown $account:$account -R "/home/$account"
chmod 755 -R "/home/$account"

Der Inhalt von /root/confs/create_db.sql ist ein weiteres, sehr simples Template:

CREATE DATABASE `$account_$project` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

Auch /root/confs/template.fcgi enthält ein Template:

#!/usr/bin/env python
import sys, os

# Add a custom Python path.
sys.path.insert(0, "/home/$account/django-projects")

# Switch to the directory of your project. (Optional.)
# os.chdir("/home/$account/django-projects/$project")

# Set the DJANGO_SETTINGS_MODULE environment variable.
os.environ['DJANGO_SETTINGS_MODULE'] = "$project.settings"

from django.core.servers.fastcgi import runfastcgi
runfastcgi(method="threaded", daemonize="false")

Das Script lässt sich folgendermassen ausführen:

~/# startproj account website

Django-Utils für den Benutzer generieren

Der Benutzer braucht ein Scripts, um die Fastcgi Sockets zu generieren, welches wir django_site nennen und in /usr/local/bin/ abspeichern:

#!/bin/bash

function usage {
    echo
    echo "'django_site' is a script that starts or stops a django project site."
    echo
    echo "Usage: ${0##*/} {start|stop} sitename"
    if [ "$*" != "" ]; then echo " Error: $*"; fi
    echo
    exit 1
}

function stopdjango {
    # check if the pidfile exist and kill
    if [ -f $1 ]; then
        kill `cat -- $1`
        rm -f -- $1
    fi

    # check if socket file exists and delete
    if [ -e $2/$3.sock ]; then
        rm -f -- $2/$3.sock
    fi
}

# check if the paremeters are given
if [ $# -lt 1 ]; then
    usage
elif [ $# -lt 2 ]; then
    usage "var 'django-project' missing"
fi

tempdir="/tmp/$USER"
djanproj=$2
djanprojdir="$HOME/django-projects/$djanproj"
pidfile="$djanprojdir/django.pid"
socket="$tempdir/$djanproj.sock"


case $1 in
    "start")
        # check if the django project exists
        if [ ! -d $djanprojdir ]; then
            usage "Django '$djanproj' project does not exist"
        fi

        #check for temporary directory and create it
        if [ ! -d $tempdir ]; then
            echo "Creating directory $tempdir ..."
            mkdir $tempdir
            chown $USER:www-data $tempdir
        fi

        stopdjango $pidfile $tempdir $djanproj

        location=`pwd`

        #change to the Django dir and execute the runfcgi command
        cd $djanprojdir
        echo "Starting django site '$djanproj' ..."
        python manage.py runfcgi socket=$socket pidfile=$pidfile
        cd $location

        # change read and write rights
        if [ -e $tempdir/$djanproj.sock ]; then
            chown $USER:www-data $tempdir/$djanproj.sock
            chmod 775 $tempdir/$djanproj.sock
            echo "Django project '$djanproj' is running..."
        else
            echo "Error: could not write $tempdir/$djanproj.sock"
            echo "Django project '$djanproj' is NOT running..."
        fi
        ;;

    "stop")
        echo "Stopping django project '$djanproj'..."
        stopdjango $pidfile $tempdir $djanproj
        ;;

esac

Der Benutzer kann dann mit diesem Script das Django-Projekt starten:

account@server:~$ django_site start website

oder stoppen:

account@server:~$ django_site stop website

Nun kann der Benutzer - in diesem Fall account - sich an die Arbeit machen und sein Django-Projekt konfigurieren und laufen lassen.

Benutzervorlage abspeichern

Wir haben nun einen gut funtionierenden Benutzeraccount, welchen wir als Vorlage benützen möchten. Dazu müssen wir lediglich alle Dateien aus dem Benutzerordner - bis auf ~/mysql.settings - nach /etc/skel kopieren.

~/# cp -R /home/account/* /etc/skel/
~/# rm -f /etc/skel/mysql.settings

Enjoy!