Lutz Prechelt, 2021-11-19
https://github.com/prechelt/anwesende
Simple attendance registration for universities having pandemics. "anwesende" is German for "people that are being present".
DE: Eine sehr einfache Lösung für die Verfolgung von Infektionsketten, die stark auf menschliche Urteilskraft und manuelle Schritte baut, um die Software einfach und den Gebrauch flexibel zu halten.
EN: A very simple web application to help tracing infection chains
at universities.
Makes heavy use of human judgment and manual operations
to keep the software simple and its use flexible.
Designed for universities in the German-language area, so parts
of the application are German+English, parts are German-language only.
The code and technical documentation (Sections 4 and 5) are in English.
Rollen:
- Dienst
a.nwesen.de
(kurz: Dienst): Die hier web-basierte Funktionalität, die die hier beschriebene Software entfaltet, wenn man sie betreibt. - Teilnehmende Hochschule (kurz: Hochschule): Eine Hochschule, die den Dienst betreibt oder mit nutzt.
- Besucher/in: Eine Person, deren Anwesenheit in einem Raum der Hochschule erfasst werden soll.
- Datenverwalter/in: Die Person, die Lesezugriff auf die erfassten Anwesenheitsdaten hat und anderen Berechtigten selektiv Zugang verschafft.
Schritte des Gesamtablaufs:
- Eine Einheit einer teilnehmenden Hochschule übermittelt der Datenverwalter/in
eine Liste von Räumen und Sitzplätzen,
siehe
Räume übermitteln
unten. - Sie erhält im Gegenzug eine PDF-Datei mit einem QR-Code für jeden Sitzplatz und klebt den QR-Code dauerhaft am zugehörigen Sitzplatz auf.
- Ein/e Besucher/in scannt den QR-Code an ihrem Sitzplatz
und gibt minimale Daten für die Verfolgung ein;
siehe
Besucher/innen/sicht
unten. - Der Dienst speichert die Platzdaten, Personendaten und den Zeitpunkt.
- Im Infektionsfall ruft die Datenverwalter/in die Anwesenheitsdaten für die
Kontaktgruppen einer infizierten Person im passenden Zeitfenster
(z.B. 3 Tage) ab,
um sie dem Gesundheitsamt zu übermitteln;
siehe
Anwesenheitsdaten abrufen
unten.
Große Teile dieser Beschreibung sind auch in der Software selbst enthalten: Der Überblick in anwesende/templates/room/home.html (Homepage), die Beschreibung der Schritte 1, 3 und 5 in anwesende/templates/room/import.html (Raum-Import), anwesende/templates/room/privacy.html und anwesende/templates/room/search.html. (Datenabfrage).
Weitere Informationen finden sich in den FAQs unter anwesende/templates/room/faq.html). (FAQs),
Rollen:
- Dienst
a.nwesen.de
, - Besucher/in ('ich') einer teilnehmenden Hochschule.
- Ich komme in einen offenen Raum der teilnehmenden Hochschule.
An meinem Sitzplatz klebt ein QR-Code. Ich scanne ihn mit meinem
Smartphone und lande in meinem Webbrowser auf einer Seite mit
ungefähr so einem Namen:
http://a.nwesen.de/1234567890
. - Der Dienst zeigt mir Datenschutzhinweise.
- Ich gebe meine Daten ein (Vorname, Name, Mobilfunknummer, Email,
Straße/Hausnummer, PLZ, Ort)
und sende das Formular ab.
Ab dem zweiten Aufruf ist das Formular wegen eines Cookies sogar schon vor-ausgefüllt und ich muss es nur absenden.
Anmerkungen:
- Der Dienst registriert nur das Kommen in einen Raum, nicht das Verlassen (in der Annahme, dass das nicht gut klappen würde, aber die betroffenen Zeiträume trotzdem meist gut eingrenzbar sind).
- Zum Scannen von QR-Codes gibt es zahllose Apps.
Einfach im PlayStore/AppStore nach
QR Code
suchen.
Rollen: Mitarbeiter/in einer teilnehmenden Hochschule, Datenverwalter/in.
- Die Mitarbeiter/in lädt sich die
Excel-Vorlage zur Raumübermittlung
und füllt sie aus:
- Die Überschriftzeile und die Zellenformate keinesfalls ändern!
- Jeder Raum bekommt eine Zeile.
- Für alle Einträge bitte genau die nachfolgenden Hinweise beachten:
organization
: Domainname der teilnehmenden Hochschule, z.B.fu-berlin.de
für die Freie Universität Berlin.
Dieser Wert ist in allen Zeilen der Datei gleich.department
: Intern gebräuchlicher (Kurz)Name der Hochschuleinheit, z.B.MathInf
für den Fachbereich Mathematik und Informatik.
Auch dieser Wert ist in der Regel in allen Zeilen der Datei gleich, weil die Mitarbeiter/in nur zu diesem einem Bereich gehört.building
: Gebäudebezeichnung, meist Straße und Hausnummer, z.B.Takustr. 9
für das Informatik-Hauptgebäude in der Takustraße 9.
Diesen Wert sollte man (genau wie alle anderen) am besten kurz halten, da er auf dem QR-Code-Schild mit ausgedruckt wird und dort die verfügbare Breite nicht überschreiten sollte.
Der Name muss nicht unbedingt exakt zum Straßenschild passen. Wenn es also von einem langen Straßennamen eine in dieser Hochschuleinheit allgemein gebräuchliche Abkürzung gibt, könnte man auch die verwenden.room
: Raumbezeichnung, in der Regel eine Raumnummer, z.B.055
für den Seminarraum 055 im Erdgeschoss.
Dies sollte zur Beschriftung an der Eingangstür des Raums passen.row_dist
: minimaler Abstand der Sitzreihen in Metern.seat_dist
: minimaler Abstand zweiter benachbarter Sitze (links/rechts) in Metern.seat_last
: Letzter Sitz, z.B.r2s7
für Reihe 2, Sitz 7 in einem Raum mit 14 Sitzen in zwei Reihen. Der erste Sitz ist immerr1s1
.
Diese Sitznamen werden auf den QR-Code-Schildern gut lesbar mit ausgedruckt. Wenn Reihen unterschiedlich viele Sitze haben, muss die maximale Anzahl auch für die letzte Reihe angegeben werden (selbst wenn die in Wirklichkeit kürzer sein sollte) und manche QR-Codes werden dann nicht mit aufgeklebt; hier ein Beispiel mitseat_last
r5s4.
Die Software nimmt je 1,4m Sitzabstand an und gibt im Schritt "Anwesenheitsdaten abrufen" resultierende Abstände mit aus.
- Die Mitarbeiter/in schickt die Excel-Datei per Email an die Datenverwalter/in.
- Die Datenverwalter/in prüft die Daten auf Plausibilität, liest die Datei in den Dienst ein und erhält eine Seite mit QR-Codes als Ergebnis. Sie enthält für jeden Sitzplatz einen QR-Code mit beschreibender Beschriftung wie es in der Excel-Datei deklariert war.
- Die Datenverwalter/in druckt diese Webseite in eine PDF-Datei und schickt sie der Mitarbeiter/in per Email.
- Die Mitarbeiter/in druckt die QR-Codes aus und klebt jeden davon
auf den entsprechenden Sitzplatz im entsprechenden Raum.
Der QR-Code sollte vollständig mit mattem transparentem Klebeband bedeckt sein, damit er lange lesbar bleibt.
Varianten:
Schritt 3b: Ist die Datei fehlerhaft und lässt sich nicht einlesen, korrigiert die Datenverwalter/in offensichtliche Fehler selbst und klärt andernfalls die Korrekturen mit der Mitarbeiter/in.
Anmerkungen:
- Obige Beschreibung gilt für den Bereich der Lehre.
Im Bürobereich wird hingegen grober erfasst:
Ein Bereich, in dem sich Beschäftigte vermutlich gelegentlich auch
länger begegnen (Korridor, Küche, Besprechungsraum, Bürobesuche),
wird zu einem "Raum" zusammengefasst, z.B. ein ganzer Flur.
Dadurch muss jede/r nur einmal pro Tag eine Eingabe machen und die Kontaktgruppen fallen eher zu groß aus als zu klein. Das ist deshalb sinnvoll, weil es wesentlich einfacher ist, eine zu lange Liste möglicher Kontakte zu bereinigen, als fehlende Kontakte aufzuspüren. - Innerhalb eines solchen "Raums" kann man dann Räume (oder Teile davon) als Sitzplätze interpretieren und die Beschäftigten anhalten, sich ggf. mehrfach anzumelden, wenn sie länger an einem anderen "Sitzplatz" (z.B. der Küche) als ihrem üblichen eigenen sind, damit bei der Nachverfolgung das Sortieren in Kontakte und Nichtkontakte von den Anmeldedaten besser unterstützt wird.
- Beispiel: A und B arbeiten gleichzeitig am "Sitzplatz" 1
(tatsächlich Raum 33).
C, D, E, F, G, H arbeiten zur selben Zeit an den "Sitzplätzen"
2, 2, 3, 4, 5, 5,
(tatsächlich den Räumen 34, 34, 35, 36, 37, 37).
Die Küche ist als "Sitzplatz" 7 registriert.
Wenn sich A infiziert, ist sofort B als Kontakt klar. Aber was ist mit B bis H?
A, C und F haben sich am fraglichen Tag auch mal am Sitzplatz 7 angemeldet. C zu einer anderen Zeit als A, aber F zu einer überlappenden.
Bei der Abfrage würden also B bis H alle in der Kontakteliste aufgeführt, aber B und F sind als wahrscheinlichste Kontakte leicht erkennbar, denn B hat den gleichen "Sitzplatz" 1 wie A und F hat den gleichen "Sitzplatz" 7 wie A. Den hat auch C, wird aber mangels Zeitüberlappung nur auf der gleichen mäßigen Risikostufe geführt wie die übrigen.
Rollen: Infiziertes Mitglied der Hochschule, Mitarbeiter/in der Hochschuleinheit, Datenverwalter/in
- Ist ein Mitglied (oder ein/e Besucher/in) der Hochschule infiziert, so informiert es die betreffenden Hochschuleinheit(en) darüber, an welchen Tagen eine Infektiösität bestanden haben kann.
- Die Mitarbeiter/in der Hochschuleinheit, die bei der Datenverwalter/in als abrufberechtigt bekannt ist, übermittelt diesen Zeitraum und die Personendaten (Name, Telefon, Email) der infizierten Person per Email an die Datenverwalterin.
- Die Datenverwalter/in ruft die zugehörigen Kontaktpersonen ab und übermittelt sie an die Mitarbeiter/in. Als Kontaktpersonen gelten alle, die Einträge im selben Raum haben, die sich zeitlich mindestens 15 Minuten mit der Anwesenheit der infizierten Person überlappen.
- Die Mitarbeiter/in bereinigt die Daten per Augenschein und Rückfragen und entfernt ggf. überzählige Einträge.
- Die Mitarbeiter/in übermittelt die bereinigten Daten dem Gesundheitsamt.
im Vergleich zu papierbasierter Erfassung der Anwesenheit
Vorteile:
- Zeitersparnis Erfassung: Die Erfassung geschieht mit viel weniger Zeit- und Arbeitsaufwand.
- Zeitersparnis Kontaktverfolgung: Im Infektionsfall kann dem Gesundheitsamt innerhalb von Minuten eine vollständige elektronische Liste der möglichen Kontaktpersonen übermittelt werden.
- Ressourcenersparnis: Spart erhebliche Mengen an Papier ein.
- Hygiene: Es müssen keine Papiere von Hand zu Hand weitergegeben werden.
- Datenschutz 1: Nur eine Person hat anlasslos Zugang zu den erfassten Daten. Im Papierfall sind dies viele und es ist schwierig zu überschauen, welche.
- Datenschutz 2: Die Daten können am Ende der Aufbewahrungsfrist vollautomatisch und somit verlässlich gelöscht werden.
Risiken:
- Datenschutz:
Ein gegebenenfalls erfolgreicher Einbruch in die Datenbank des Servers
beträfe mehr Daten als
ein Diebstahl papierner Erfassungsbögen.
(Das Risiko ist klein, denn die Datenbank ist für Angreifer nicht attraktiv, weil sie keine wertvollen Daten wie Bankdaten oder Passwörter enthält.) - Vollständigkeit: Der Erfassungsvorgang ist für die anderen Anwesenden weniger anschaulich und kann deshalb möglicherweise leichter vergessen werden.
- Richtigkeit: Unsinnige Eingaben von Besuchern ("Donald Duck") sind kaum mehr an Ort und Stelle zu entdecken.
- Benötigt eine Einweisung (ca. 1 Stunde).
- Muss Mitglied einer teilnehmenden Hochschule sein. Alle anderen teilnehmenden Hochschulen müssen mit dieser Hochschule einen Auftragsdatenverarbeitungsvertrag schließen.
- Sollte bei jeder Meldung von Räumen aus einer Hochschuleinheit die Excel-Datei durchsehen und nötigenfalls die Schreibweisen (insbesondere von Hochschuleinheiten) vereinheitlichen.
- Muss bei jeder Meldung von Räumen aus einer Hochschuleinheit klären, wer für diese Einheit berechtigt ist, Anwesenheitsdaten abzurufen, und dann den Datenzugang auf diesen Personenkreis beschränken.
Everything beyond this point is technical information, therefore in English.
The application is meant to be deployed locally in most organizations
(to simplify the situation regarding privacy protection)
and allows some configuration to adapt to local needs.
It is designed such that one installation can serve several smaller
organizations together, though.
The service is build using Python, Django, PostgreSQL, Gunicorn, and Traefik. The deployment procedure described below will obtain these pieces and configure them. The code organization roughly follows the cookiecutter-django template and the Python code is structured Django-style.
(If you find errors anywhere, please speak up!)
The deployment procedure assumes an existing infrastructure of
Linux, bash, ssh, rsync, and Docker 18.09 or younger
(with docker-compose 1.21 or younger).
The deploying user must be a member of the docker
group.
There are six possible styles of deployment:
DEPLOYMODE=CERTS
: a stand-alone configuration that brings its own Traefik web server and relies on a manually created certificate for https. This configuration uses three docker containers: traefik, django, postgres.DEPLOYMODE=LETSENCRYPT
: a variant of the above that relies on Let's Encrypt to generate the certificates for https.DEPLOYMODE=GUNICORN
: a configuration without webserver that exposes the service port of the Gunicorn application server to be used by an existing webserver that is capable of https. This configuration uses only two docker containers: django, postgres.
All three of these can be deployed locally on a single server (REMOTE=0
)
or have their docker images build on a local machine
and deployed onto a remote server via a docker registry
(REMOTE=1
, resulting in another three styles of the same three DEPLOYMODEs).
In any case, there are only few manual steps; most work is done by a
few calls to a script called anw.sh
.
(If you want to deploy without docker, see GitHub Issue 10 for hints.)
- Create a working directory anywhere on a Linux machine
(called the 'source machine').
If your server is in a DMZ and cannot connect to other servers itself,
this must be a separate machine (
REMOTE=1
). Performgit clone https://git.imp.fu-berlin.de/anwesende/anwesende.git
. Rename the directory if you want, e.g.:mv anwesende anw
,
and go there:cd anw
.
This working directory is the reference for all subsequent commands. - Do
./anw.sh - prepare_envs
. This creates two new files:.envs/myenv.env
is a docker environment file containing various application settings. Carefully review the comments in that file and insert appropriate values for each setting..envs/production.sh
is a shell environment file containing various deployment settings. Carefully set appropriate values here, too.
- If
REMOTE=1
, call./anw.sh production docker_login
and type your password twice to log into the docker registry locally and on the server. (IfREMOTE=0
, skip this step.) - Do
./anw.sh production install
. If all goes well, this command will- build the docker images on the source machine
- if
REMOTE=1
, transfer them to the server's docker service via the registry and copy a handful of configuration files to the server's~/anw
directory. - create and start the docker containers on the server. Carefully review the output for error messages. Repair those problems and repeat the command.
- For
DEPLOYMODE=CERTS
only:
Put your certificate at$VOLUME_SERVERDIR_TRAEFIK_SSL/certs/anwesende.pem
and your private key at$VOLUME_SERVERDIR_TRAEFIK_SSL/private/anwesende-key.pem
. Make directoryprivate
readable for root only.
(Alternatively, you can pre-create these directories yourself before you start the traefik container. Anybody can be the owner then; traefik does not care.) - For
DEPLOYMODE=GUNICORN
only:- The django container runs the Gunicorn application server, which will
expect requests via http (and only http) on port GUNICORN_PORT as
defined in
.envs/production.sh
. - Define a name prefix on your webserver, for which it will
remove the name prefix from the request and then forward the rewritten
request to
yourserverhost:${GUNICORN_PORT}
(e.g.localhost:5000
).
For instance, for Apache (if running on the same server), this might mean adding the file/etc/apache2/conf-available/anwesende_proxy.conf
containingand then performProxyRequests Off ProxyPass "/anw" "http://localhost:5000" ProxyPassReverse "/anw" "http://localhost:5000"
a2enconf anwesende_proxy; systemctl reload apache2
.
- The django container runs the Gunicorn application server, which will
expect requests via http (and only http) on port GUNICORN_PORT as
defined in
Steps 3 to 6 can be repeated as needed with no harm.
To start over from step 1, remove the reference directory (and beware
that this will delete your settings in .envs
as well).
- If all went well, your anwesende server should now be reachable.
Let's assume it is calledanwesende.some-university.de
. Start a web browser and visithttps://anwesende.some-university.de
. - Works?
Then now setPATCH_DJANGO_HTTPS_INSIST=True
in.envs/production.sh
and repeat step 4 above. Congratulations! - Does not work?
Then consider the following ideas for your debugging: - Step 4 has substep commands that you can call independently.
- Execute
./anw.sh - help
to see them. - Look into the file
anw.sh
how they are used ininstall()
. - You can teach your bash autocompletion of these command names by executing
./anw.sh - completions
(which prints a long command) and then executing the command that it printed. (Backticks do not work properly for this for some reason.) - In a
REMOTE=1
setting, be aware that you can thoroughly confuse yourself with these commands if you fail to includeonserver
withdocker-compose
ordocker
, because your local docker will comply with them gladly, but not change anything on the server.
- Execute
- In
DEPLOYMODE=LETSENCRYPT
, you may have to wait 2 minutes for the certificates to arrive. - Consult the logs, either from remote (if
REMOTE=1
) by./anw.sh production onserver docker-compose logs
or right on the server bydcoker-compose logs
. - In
DEPLOYMODE=CERTS
, traefik will ignore your certificate if it does not match the request (different fully-qualified domain name). No log entry is created if this happens. - For https trouble, study the SECURITY block in the
config/settings/production.py
configuration file (in particular theSECURE_HSTS_SECONDS
setting) in order to understand what you are working with during https debugging. - The simplest setup is using
DEPLOYMODE=GUNICORN
and then contacting gunicorn on theGUNICORN_PORT
directly. If you have fundamental doubts whether the postgres and django containers work, try that.
- Create a cronjob with the following script (insert the proper directory name).
It serves two purposes: Delete data after the retention period and
make database backups.
Add a suitable
#!/bin/bash cd /home/thedeployer/anw/prod # adjust this! We need docker-compose.yml docker-compose run --rm django python manage.py delete_outdated_data # to obey DATA_RETENTION_DAYS docker-compose exec postgres backup
logrotate
call to avoid accumulating too many backups. - If you ever need to restore a backup:
- Copy the backup file to directory
$VOLUME_SERVERDIR_POSTGRES_BACKUP
. Let us assume it is calledmybackup.sql.gz
. - Go to the directory where your
docker-compose.yml
is and performdocker-compose kill django docker-compose exec postgres restore mybackup.sql.gz docker-compose restart django
- Copy the backup file to directory
- If you ever wonder what version you are running, try this:
docker inspect anw_prod_django | grep anwesende.build
- Purely optional:
There is a simple, stand-alone load testing script in
anwesende/room/tests/loadtest.py
with which you can get a rough estimate of your server's performance.
There is one installation of anwesende that is special:
The one at http://a.nwesen.de
(no https here!).
It can be used by all other installations for creating short URLs.
(The shorter the URLs specified by the QR codes, the more robust these
codes can be against scratching, chocolate taints etc.)
So instead of seat URLs like https://anwesende.some-university.de/S12345abcde
your installation can use URLs like http://a.nwesen.de/z/S12345abcde
which will simply redirect to the corresponding one above.
How?
- You send me your installation URL such as
https://anwesende.some-university.de
orhttps://www.some-university.de/services/anwesende
or whatever it may be and ask me for aSHORTURL_PREFIX
. - I assign a prefix, such as
http://a.nwesen.de/p
, enter it into the configuration of the central installation, and tell you about it. - You enter it into your settings file at
.envs/myenv.env
(for instanceSHORTURL_PREFIX=http://a.nwesen.de/p
) and repeat./anw.sh production install
.
If you do not want to use the short URL service,
simply set SHORTURL_PREFIX to your installation URL instead,
e.g. https://anwesende.some-university.de
.
If you have created QR codes previously, make sure the URLs they
show continue to work.
(Please do not try to guess what SHORTURL_PREFIX
you are going
to get; race conditions may occur.)
Your service is now ready to be used. Time to create the Datenverwalter accounts!
Once the server is running and you can retrieve the homepage properly, perform the following steps once:
- Perform
./anw.sh production onserver docker-compose run --rm django python manage.py createsuperuser --username superuser
. You could use a different username if you prefer. Enter a (near-irrelevant) email address and password for the superuser. - In a browser, visit
https://anwesende.some-university.de/admin
, log in as the superuser, and create two-or-so personal accounts for the Datenverwalters athttps://anwesende.some-university.de/admin/users/user/
.- Enter strings for name (fullname), firstname, lastname, email.
- Do not change any of the checkboxes.
- Under "Groups:" pick group "datenverwalter" and add it to "Chosen groups".
Save.
This group membership is what gives an account the Datenverwalter privilege. The superuser account does not (and should not) have that privilege.
Done!
The anw.sh
script supports a seventh mode: DEPLOYMODE=DEVELOPMENT
.
It uses only a single docker container for the database (with a
data volume that is internal to docker) and
runs tests or the Django development webserver directly in a local shell.
The configuration file development.sh
for it will look more or less like this:
source .envs/production.sh
DEPLOYMODE=DEVELOPMENT
REMOTE=0
PATCH_DJANGO_SETTINGS_MODULE=config.settings.local
PATCH_DJANGO_HTTPS_INSIST=False
PATCH_POSTGRES_HOST=localhost
LOCAL_POSTGRES_PORT=5432
To use it,
- execute
./anw.sh development build_images
once (and then probably never again, - turn the database server on and off as needed by
./anw.sh development server_up
and./anw.sh development server_down
, respectively, - with the database server turned on, run the following two commands once:
python manage.py make_base_data
python manage.py createsuperuser
and proceed to create a user as described under "initiating operation" above. - run tests by
pytest
(with arguments as needed), and - run the development webserver by
python manage.py runserver
.
The application is written in Python using the Django framework and a PostgreSQL database. It is open-source (with an MIT license) in order to provide maximal transparency.
For information about the changes in the individual versions of a.nwesen.de (including upcoming changes), please see RELEASES.md.