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.This is an old revision of the document!
Table of Contents
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 = Trueliefert Django die statischen Files aus, beiDEBUG = Falsenicht, 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_HOSTSeingetragen 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.
