User Tools

Site Tools


server:openeats

This is an old revision of the document!


OpenEats

Bei OpenEats handelt es sich um einen in Python geschriebenen Django-Server zur Verwaltung von Rezepten. Da das Projekt von dem ursprünglichen Entwickler nicht mehr fortgeführt wurde, sind zahlreiche Forks entstanden. Dabei viel mir einer ins Auge, der optisch und von der Funktionalität her ansprechend gestaltet war, aber leider auch das JavaScript-Framework React übergestülpt bekam. Trotz meiner Vorbehalte gegenüber Node.js 1) und JavaScript im Allgemeinen habe ich diesem Fork eine Chance gegeben.

Vorbereitung

Der Python-Part wird in einer virtuellen Umgebung eingerichtet, wozu die folgenden Pakete installiert werden müssen, sofern nicht bereits vorhanden:

$ sudo pacman -S python python-pip python-virtualenv

Für den Javascript-Teil benötigen wir den Node.js Paket-Manager npm:

$ sudo pacman -S npm

Datenbank anlegen

Für die Datenbank verwende ich MySQL in Form von MariaDB. Dort wird die Datenbankstruktur angelegt und ein Benutzer angelegt.

  # mysql -u root -p
  mysql> CREATE DATABASE openeats;
  mysql> CREATE USER openeats@localhost IDENTIFIED BY 'MYSUPERSECRETPASSWORD';
  mysql> GRANT ALL PRIVILEGES ON openeats.* TO 'openeats'@'localhost' IDENTIFIED BY 'MYSUPERSECRETPASSWORD';
  mysql> FLUSH PRIVILEGES;
  mysql> EXIT

Dabei sollte natürlich ein sicheres Passwort verwendet werden.

nginx

nginx fungiert später als Reverse Proxy, um die Seiten der lokal laufenden Djando-, bzw. Node-Server auszuliefern.

/etc/nginx/ngnix.conf
    server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name food.domain.tld;

        ssl_certificate /etc/letsencrypt/live/domain.tld/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/domain.tld/privkey.pem;
        ssl_trusted_certificate /etc/letsencrypt/live/domain.tld/fullchain.pem;

        location / {
            proxy_pass http://localhost:8080;
            proxy_set_header Host $host;
            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_redirect http://localhost:8080 https://food.domain.tld;
        }

        location /admin/ {
            proxy_pass http://127.0.0.1:8000;
            proxy_set_header Host $host;
            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_redirect http://localhost:8000 https://food.domain.tld;
        }

        location /api/ {
            proxy_pass http://127.0.0.1:8000;
            proxy_set_header Host $host;
            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;
        }

        location /static-files/ {
            autoindex on;
            root /usr/share/webapps/openeats/api/;
            try_files $uri /static-files/$uri;
        }

        location /site-media/ {
            autoindex on;
            root /usr/share/webapps/openeats/api/;
            try_files $uri /site-media/$uri;
        }
    }

Die Einrichtung der Zertifikate für die TLS-Verschlüsselung ist hier beschrieben.

Projekt klonen

Ich arbeite im Folgenden als root, mit su Benutzer wechseln. Installieren will ich das Projekt in /usr/share/webapps.

# cd /usr/share/webapps/
# git clone https://github.com/RyanNoelk/openeats.git
# cd openeats/

Django einrichten

Zuerst wird die virtuelle Umgebung angelegt, in der der Python-Teil läuft. Ich habe sie hier exemplarisch virt_env genannt:

# mkdir virt_env
# virtualenv virt_env/
# source virt_env/bin/activate

Nun befinden wir uns in der virtuellen Umgebung.

Bevor die Python-Pakete installiert werden können, muss noch ein Fehler behoben werden, der bei der Verwendung von Python 3 wichtig sind. Das in api/base/requirements.txt angegebene MySQL-python ist nicht kompatibel mit Python 3. Ein Fix ist, dies durch mysqlclient zu ersetzen, einem Fork von MySQL-python, der kompatibel mit Python 3 ist. Dazu in der requirements.txt die Zeile

MySQL-python==1.2.5

(Zeile 16, Stand Oktober/November 2017) austauschen mit

mysqlclient==1.3.12

Nun können die Pakete installiert werden:

# pip install -r api/base/requirements.txt

Als nächstes legen wir eine Datei mit unseren lokalen Einstellungen an

# vim api/base/local_settings.py

mit folgendem Inhalt:

api/base/local_settings.py
SECRET_KEY = 'SUPERSECURERANDOMSECRETKEY'
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'openeats',
        'USER': 'openeats',
        'PASSWORD': 'MYSUPERSECRETPASSWORD',
        'HOST': '',
        'PORT': '3306',
        'OPTIONS': {
            'charset': 'utf8mb4'
        }
    }
}
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'node', 'food.domain.tld']
LANGUAGE_CODE = 'de-DE'

Einen Secret Key kann man sich beispielsweise auf dieser Seite generieren.

Damit die lokalen Einstellungen, die wir soeben angelegt haben, auch gefunden werden, muss noch ein Fehler in der settings.py behoben werden. Dazu ganz am Ende der Datei in der Zeile

    from local_settings import *

einen Punkt vor local_settings machen. Dies ist nötig, da Python 3 keine implizite relative Imports durchführt. from local_settings ist in Python 3 ein absoluter Import. from .local_settings hingegen ist ein expliziter relativer Import, welcher ebenfalls mit Python 2.7 funktioniert, s. PEP 328.

# vim api/base/settings.py

Nachdem dieser Bug beseitigt ist, sollte alles vorbereitet sein und die eigentliche Einrichtung kann beginnen.

Um die Datenbank zu befüllen, führen wir migrate mit Django aus:

# ./api/manage.py migrate

Als nächstes erstellen wir einen Superuser als Admin für die Seite und befolgen die Anweisungen:

# ./api/manage.py createsuperuser

Mit loaddata werden ein paar Informationen in die Datenbank gefüllt.

# ./api/manage.py loaddata course_data.json
# ./api/manage.py loaddata cuisine_data.json

Nun sammeln wir noch die statischen Dateien für das Django REST Framework:

# ./api/manage.py collectstatic

Zum Testen können wir nun den Server mit Django starten

# ./api/manage.py runserver

Im Browser sollte unter der URL https://food.domain.tld/admin/ nun der Login in den Admin-Bereich des Django-Servers erscheinen.

Einige Anmerkungen:

  • Mit DEBUG = True liefert Django die statischen Files aus, bei DEBUG = False nicht, dazu sollte der verwendete Webserver verwendet werden (Infos dazu: https://stackoverflow.com/a/12773084)
  • Bad Request (400) kann sein, dass die falsche Domain in ALLOWED_HOSTS eingetragen ist

Wenn alles klappt und man sich über das Django-Interface einloggen und Daten editieren kann, hat für das Python-Backend alles funktioniert. Als nächstes folgt das JavaScript-Frontend React, welches als nächstes eingerichtet wird.

React einrichten

Dazu am besten ein neues Terminal verwenden und den Django-Server weiter laufen lassen, da dieser über dessen API zur Abfrage der Daten benötigt wird.

Als root (su) in den für das JavaScript-Frontend vorgesehenen Ordner wechseln und die Node.js-Pakete installieren:

# cd /usr/share/webapps/openeats/frontend
# npm install

Der Befehl npm install dauert eine Weile; dabei ist es wichtig, ihn als root und nicht mit sudo auszuführen, da die Installation der Pakete sonst aufgrund fehlender Schreibrechte fehlschlägt, bzw. in eine Endlosschleife laufen kann!

Ist die Installation abgeschlossen, können wir den Node-Server starten. Dabei ist das Setzen der Umgebungsvariablen wichtig, da webpack (js Kram…) sonst Fehler liefert:

# NODE_ENV=production NODE_LOCALE=de NODE_API_URL=https://food.domain.tld npm start

Nun sollte alles funktionieren und mit dem Aufruf der URL https://food.domain.tld im Browser sollte die eigentliche Oberfläche zur Anzeige der Rezepte erscheinen.


Quelle hierfür mit mieser Dokumentation, die nicht komplett funktioniert / die Hälfte fehlt: https://github.com/RyanNoelk/OpenEats/blob/master/docs/Running_without_Docker.md

Einfacher wäre es sicherlich mit Docker, aber auf meinem Server läuft Linux mit dem Kernel 3.8. Für Docker wird aber mindestens 3.10 benötigt, da erst dort die nötigen Infrastrukturen existieren.

Server manuell starten

Um die Server manuell zu starten, sind zwei Terminal-Sitzungen nötig: Eine für den Django-Server und eine für Node.js.

Django-Server starten:

# cd /usr/share/webapps/openeats
# source virt_env/bin/activate
# ./api/manage.py runserver

Node-Server starten:

# cd /usr/share/webapps/openeats/frontend
# NODE_ENV=production NODE_LOCALE=de NODE_API_URL=https://food.domain.tld npm start

Autostart umsetzen

Die vorhin gestarteten Server werden nun nicht mehr benötigt, da der Startprozess für die beiden Server nun automatisiert wird. Am besten eine neue Terminal-Session starten, um sicher zu sein, dass keine bereits gesetzten Umgebungsvariablen fehlen und der Start der Server fehlschlägt.

Django

Über die requirements.txt wurde bereits das gunicorn Python-Modul installiert. Dieses wird verwendet, um den Django-Server als Daemon zu starten. Dazu ändern wir die Datei api/base/gunicorn_start.sh ab mit folgendem Inhalt:

api/base/gunicorn_start.sh
#!/usr/bin/env sh
 
NAME="OpenEats"
DJANGODIR=/usr/share/webapps/openeats/api
USER=root
GROUP=root
NUM_WORKERS=4
DJANGO_SETTINGS_MODULE=base.settings
DJANGO_WSGI_MODULE=base.wsgi
API_URL=localhost:8000
 
echo "Starting $NAME as `whoami`"
 
# Activate the virtual environment
source /usr/share/webapps/openeats/virt_env/bin/activate
cd $DJANGODIR
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH
 
# Start your Django Unicorn
# Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon)
exec gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $NUM_WORKERS \
  --user=$USER --group=$GROUP \
  --bind=$API_URL

Zum Testen am besten ausprobieren, ob der Server ohne Fehler startet:

# ./api/base/gunicorn_start.sh

Funktioniert alles, kümmern wir uns als nächstes darum, dass der Startprozess automatisch von systemd übernommen wird. Für Django/gunicorn erstellen wir die Datei /usr/lib/systemd/system/openeats_gunicorn.service mit folgendem Inhalt:

/usr/lib/systemd/system/openeats_gunicorn.service
[Unit]
Description=OpenEats gunicorn daemon

[Service]
Type=simple
User=root
Group=root
ExecStart=/usr/share/webapps/openeats/api/base/gunicorn_start.sh

[Install]
WantedBy=multi-user.target

Das ganze wird mit

# systemctl enable openeats_gunicorn

aktiviert. Noch ein

# systemctl start openeats_gunicorn

und der Service ist gestartet und sollte nun unter https://food.domain.tld/admin erreichbar sein. Um zu testen, dass der Autostart funktioniert, am besten den Computer neustarten und mit systemctl status openeats_gunicorn testen, ob der Service läuft.


Quelle mit Infos zu gunicorn als Daemon: http://tutos.readthedocs.io/en/latest/source/ndg.html#gunicorn-and-daemonizing-it

Node

Als nächstes erstellen wir die Datei /usr/lib/systemd/system/openeats_node.service für den Node-Server mit dem Inhalt:

/usr/lib/systemd/system/openeats_node.service
[Unit]
Description=OpenEats node daemon
After=syslog.target network.target openeats_gunicorn.service
Requires=openeats_gunicorn.service

[Service]
Restart=always
User=root
Group=root
Environment=PATH=/usr/bin:/usr/local/bin
Environment=NODE_ENV=production
Environment=NODE_LOCALE=de
Environment=NODE_API_URL=https://food.domain.tld
WorkingDirectory=/usr/share/webapps/openeats/frontend
ExecStart=/usr/bin/npm start

[Install]
WantedBy=multi-user.target

Wie vorhin den Serive mit

# systemctl enable openeats_node

aktivieren und zum Testen mit systemctl start openeats_node starten, um zu sehen, ob alles funktioniert und unter https://food.domain.tld der erwartete Inhalt erscheint. Dabei beachten, dass der Server eine Weile zum Starten braucht.

Nun sollte alles nach einem Neustart automatisch gestartet werden, wobei die entsprechende Startzeit für den Node-Server beachtet werden sollte.

1)
Mich stören die vielen (npm install – may take several minutes…) teils sinnlosen Abhängigkeiten (2016 beschloss ein Entwickler, seine Module von NPM zu entfernen, darunter ein Paket mit 11 Zeilen, um einen String links mit Nullen oder Leerzeichen aufzufüllen – dieses Paket war eine Abhängigkeit sehr vieler Projekte, die plötzlich nicht mehr funktionierten) und die daraus resultierenden aufgeblasenen Projekte, sowie die Tatsache, JavaScript auf Server-Seite zu verlagern, während viele auf Node-Pakete und Frameworks zurückgreifen, um ihr Unverständnis der Sprache oder Unwillen zum selbst Nachdenken zu kaschieren.
server/openeats.1510157126.txt.gz · Last modified: by sascha