Timelimit.io Zeitkontrolle fürs Kindersmartphone, selbstgehostet und FOSS
Eine der wichtigen Punkte, die man als Eltern gerne bei der Smartphonenutzung des eigenen Kindes kontrollieren möchte, ist der Zeitumfang in dem bestimmte Apps oder Kategorien von Apps genutzt werden können. Außerdem möchte man bestimmte Apps , wie z.B. den Playstore eventuell komplett verbieten. Unter Android gibt es hierfür neben vielen Kostenpflichtigen Apps mit Speicherung der Nutzungsdaten auf den Servern des Herstellers auch eine quelloffene alternative, die man auch selbst hosten kann, in zwei Varianten: Zum einen gibt es im Google Playstore
TimeLimit.io die komplette Version mit Unerstützung für den Timelimit-Server. Über diese Version ist es nicht nur möglich ein Kinderprofil auf mehrere Geräte anzuwenden, sondern auch die Möglichkeit die einstellungen für das Kinderprofil vom elterlichen Smartphone aus zu ändern. Wer keinen eigenen Server betreiben kann oder will und auch dem Server vom App Entwickler Jonas Lochmann nicht vertraut, der kann die komplett Lokale alternative
Open TimeLimit auf das Smartphone des Kindes installieren.
Ich habe in ermangelung eines eigenen Timelimit-Servers und einer zum damaligen Zeitpunkt eher dürftigen Dokumentation beim Smartphone meines Sohnes auch zunächst die Lokale variante eingesetzt. Auch diese funktionierte im letzten halben Jahr Überaus gut, hat aber einen gewichtigen Nachteil: Wann immer mal eine App, und sei es nur ausnahmsweise zugelassen werden soll, muss das smartphone des Kindes griffbereit sein. Und wenn sich das Kind gerate bei Freunden oder Verwandten aufhält, kann man nur auf eine gute Verbindung für "TeamViewer" als Workaround hoffen.
Timelimit-Server Repository, Dokumentation, Dockerimage
Aktuell ist mal wieder eine solche situation eingetreten. Mein Sohn ist derzeit im Urlaub mit Oma und natürlich ist das mobile Internet schlecht. Auf die Netzwerkkonfiguration darf mein Sohn aber grundsätzlich nicht zugreifen und kann dadurch das Hotel WLAN nicht einrichten. Da ich letzte Woche auch vom übrigen Teil meiner Familie "erlöst" war, hatte ich aber abends mal Zeit mich mit der Thematik wieder näher zu beschäftigen.
Es stellt sich heraus, dass der
Timelimit-Server auf codeberg.org mitlerweile sehr gut Dokumentiert ist. Einziger Wermutstropfen: Um den Server schnell mal mit Docker auf einer eigenen Maschine zu hosten, muss man das Repo erst herunterladen und dann selbst ein Image bauen, da hier kein automatisch gebautes Image existiert(e).
Das musste ich einfach korrigieren. Deshalb findet man nun ein fertiges Dockerimage als
registry.gitlab.com/lobi/timelimit-server
. Die Dazugehörige GitLab Pipeline kann über mein
Timelimit-Server Gitlab-CI Projekt eingesehen und die von mir erstellten Dateien auch frei verwendet werden (MIT Lizenz). Meine Pipeline cloned nun täglich das Original Repository und baut den aktuellen Stand vom Master-Branch als Dockerimage. Da das Original Repository keine Tags oder sonstige markierung für Releases hat, die ich identifizieren konnte gehe ich zur Zeit davon aus, dass der Stand auf dem Master-Branch grundsätzlich als Stabil angesehen werden kann und daher über den
latest
Tag bereit gestellt werden kann. Falls mal eine bestimmte Version verwendet werden soll bietet die CI hierfür auch zumindest eine kleine Lösung. Das Image wird auch immer über
registry.gitlab.com/lobi/timelimit-server/commits:<commit_hash>
bereit gestellt. Da der Commit Hash einzigartig ist, werden diese Images nicht überschrieben.
Timelimit-Server selbst hosten
Um Timelimit-Server selbst zu betreiben braucht es einen eigenen Server mit einem Linux OS. Ich beschreibe hier die Einrichtung auf meiner kleinen Hardware zu Hause, mit Ubuntu 18.04. Worauf ich nicht im detail eingehe, da diese Vorraussetzungen auf meinem System bereits erfüllt waren: Es muss ein MySQL oder PostgresSQL Server installiert sein. Ich verwende bei mir MariaDB, die Community-Alternative zu MySQL. Für die einfache Einrichtung von neuen MySQL Datenbanken empfehle ich zudem PHPMyAdmin zu installieren. Als Webserver für PHPMyAdmin und als Reverseproxy verwende ich Apache2. Im Timelimit-Server Source Projekt ist ein Konfigurationsbeispiel für Nginx zu finden. Es wird außerdem Docker und Docker-Compose benötigt. Gute Anleitungen zur Einrichtung eines LAMP (Linux, Apache, MySQL, PHP) Servers finden sich unter anderem auf
HowtoForge. Eine PHP Installation wird hier allerdings nicht benötigt.
Datenbank anlegen
Unter PHPMyAdmin lässt sich sehr einfach ein neuer Nutzer anlegen. Da wir über das Netzwerkinterface von Docker und nicht über die Loopback adresse auf die Datenbank zugreifen müssen ist es wichtig für den Benutzer den host auf "jeder Host" /
%
einzustellen. Wenn das Dockernetzwerk wie meistens auf
172.17.0.0/16
sollte auch
172.17.%
als genauere einschränkung funktionieren. Man aktiviert die Checkboxen "Erstelle eine Datenbank mit gleichem Namen und gewähre alle Rechte." und "Gewähre alle Rechte auf Datenbanken die mit dem Benutzernamen beginnen". Im übrigen sollte der Benutzer
keine weiteren Rechte benötigen. Das Passwort für den Benutzer generiert man am besten und legt es zur Sicherung in einem sicheren Passwortspeicher wie KeePass ab.
Docker-Compose konfiguration
Hat man wie ich bereits einen MySQL Server und einen Apache2 Webserver am laufen, braucht man diese komponenten nicht über Docker-Compose konfigurieren. Am besten legt man sich für die
docker-compose.yml
ein eigenes Verzeichnis an.
- mkdir timelimit-server
- cd timelimit-server
Mit dem Editor der Wahl, erstellt man sich in das Verzeichnis eine
docker-compose.yml
in der Art:
version: '3'
services:
timelimit-server:
image: 'registry.gitlab.com/lobi/timelimit-server'
container_name: timelimit_server
environment:
NODE_ENV: 'production'
DATABASE_URL: 'mariadb://timelimit:CHANGE_PASSWORD@db:3306/timelimit'
PORT: '8080'
MAIL_SENDER: 'timelimit@yourdomain.org'
MAIL_TRANSPORT: '{"host": "smtp.provider.com", "port": 465, "secure": true, "auth": {"user": "username", "pass": "your password"}}'
ADMIN_TOKEN: 'verry long generated token'
MAIL_WHITELIST: >
one.allowed@mailadress.com,
second.allowed@provider.de
restart: 'always'
dns: '192.169.179.2' # angenommen auf dieser adresse betreibt man seinen
# eigenen DNS-Server
extra_hosts:
- 'db:172.17.0.1'
network_mode: 'bridge'
ports:
- '127.0.0.42:8001:8080'
In der Oben stehenden Datei werden die Zugangsdaten für den neuen Datenbankbenutzer benötigt und es mus in der
DATABASE_URL
nach dem
/
der Name der angelegten Datenbank angegeben werden. Damit man später die Admin funktionen über die REST-API nutzen kann, z.B. freischalten der Pro Funktionen aktiviert man diese mit setzen des
ADMIN_TOKEN
. Dieser sollte natürlich auch generiert sein und
keinem anderen Passwort entsprechen.
Wichtig ist hier auch der
extra_host
Eintrag. Im
bridge
Netzwerkmodus von Docker ist der Host (nicht der Container) i.d.R. unter
172.17.0.1
erreichbar. Docker macht durch die angegebene Konfiguration die Namensauflösung des Hostname
db
zur angegebenen Adresse bekannt.
Über die Umgebungsvariable
MAIL_WHITELIST
lassen sich mit Komma getrennt die erlaubten E-Mail Adressen oder Domains angeben. Damit ist es dann nur den angegebenen Adressen möglich einen Familienaccount für den Dienst an zu legen. Man muss also nicht fürchten auf einmal ungewollt auch für andere einen Dienst anzubieten, nur weil man seinen Dienst global erreichbar haben möchte.
Durch die angegebene Port konfiguration ist gewährleistet, dass der Container nur unter einer Loopbackadresse erreichbar und dmait nicht automatisch im Netzwerk verfügbar ist. Damit steht einem Starten des Dienstes nichts mehr im wege. Die erforderliche SSL Verschlüsselung nach außen erhält man dann mit Apache2 oder Nginx.
Ein guter erster Test ist ein erster Aufruf über curl:
- curl %eY9-!://127.0.0.42:8001/time
Es sollte ein Timestamp in einer JSON Struktur zurück gegeben werden.
Ich habe mir in der
/etc/hosts
einen Eintrag für
127,0.0.42
hinzugefügt und dafür
timelimit-server
als Namen hinterlegt.
#/etc/hosts
127.0.0.42 timelimit-server
Das ist nicht notwendig macht aber die Testabfragen und die folgende Apache2 Konfiguration etwas schöner:
curl http://timelimit-server:8001/time
.
SSL über HTTP Reverseproxy mit Apache2 und Let's encrypt
SSL Zertifikate lässt man sich, sofern man keine bessere Lösung hat, am besten von Let
s encrypt signieren. Das geht einfach und automatisierbar über
certbot` und man erhält ein Zertifikat, das zu recht keine Warnungen im Browser auslöst. Wichtig, damit das ganze funktioniert ist natürlich, dass die eigene Domain oder Subdomain öffentlich aufgelöst werden kann.
certbot certonly --apache -d timelimit.example.com
Erzeugt ein Zertifikat für
timelimit.example.com
unter Beachtung, dass aktuell ein Apache Webserver läuft und legt die Zertifikatsdateien und den dazugehörigen privaten Schlüssel am in der ausgabe angegebenen ort ab.
Unter Debian basierten Linux Distributionen liegen die Seitenkonfigurationen unter
/etc/apache2/sites-available/
. Hier legt man sich für den neuen VirtualHost eine eigene Datei an. Es ist gute praxis als namen die Domain zu verwenden unter der der Service erreichbar sein soll. Im Falle von
timelimit.example.com
wäre das eine neue Datei
timelimit.example.com.conf
mit folgendem Inhalt:
# /etc/apache2/sites-available/timelimit.example.com.conf
<VirtualHost timelimit.example.com:443>
ServerName timelimit.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/timelimit.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/timelimit.example.com/privkey.pem
ProxyPreserveHost On
ProxyPass / http://timelimit-server:8001/
ProxyPassReverse / http://timelimit-server:8001/
RewriteEngine On
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule /(.*) ws://timelimit-server:8001/$1 [P,L]
ProxyRequests off
</VirtualHost>
Besonders spannend hier sind die Reqrite Parameter. In der
Beispiel Nginx Konfiguration aus dem Source-Repository wird folgendes gesetzt:
# ...
# the following is required for websocket support
#
# without websockets, the client will not detect
# that there is a connection and it will not sync
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# ...
Daraus lässt sich vermuten, dass im Apache2 ebenfalls eine Direktive zum ändern des Headers verwendet werden muss. Stattdessen kommt aber die Rewrite Engine zum einsatz. Die benötigten Header setzt Apache2 dann offenbar automatisch. Das ganze wird benötigt damit der Client über den Proxy auch eine Websocket Verbindung herstellen kann. Ohne Websocket funktioniert in der App z.B. das aktivieren des Gerätes als aktuell durch das Kind genutztes Gerät nicht. Eine gute Stelle an der man testen kann ob der ReverseProxy auch Websocket Verbindungen korrekt unterstützt. Hier hatte ich ein klein wenig zu tun die richtige Lösung zu finden.
Ich habe bei mir zum lokalen testen noch eine Zugriffsbeschränkung eingezogen:
# /etc/apache2/sites-available/timelimit.example.com.conf
<VirtualHost timelimit.example.com:443>
# ...
<location /*>
Order Deny,Allow
Deny from all
Allow from 192.168.178.0/24 # Heimnetzwerk, kann auch eine andere Netzadresse sein
Allow from 127.0.0.0/8
</location>
# ...
</VirtualHost>
Die neue Seite lässt sich über
a2ensite timelimit.exammple.com
aktivieren. Möchte man mal die Seite deaktivieren geht dies über à 2dissite seitenname`
Wenn man sicher gehen möchte, dass für alle VirtualHosts HTTPS verwendet werden soll, kann man die datei
/etc/apache2/sites-available/000-default.conf
wie folgt gestalten:
# /etc/apache2/sites-available/000-default.conf
<VirtualHost 192.168.178.140:80>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
Redirect permanent / https://%{HTTP_HOST}%{REQUEST_URI}
</VirtualHost>
Damit wird allen Anfragen mit einem Permanenten Redirect auf die ursprünglich angefragte Resource nur eben mit HTTPS davor geantwortet. Guten clients ermöglicht diese Permantente Umleitung zukünftig direkt die sichere Variante an zu fragen.
Premium aktivieren
Einige der nützlichen Appfunktionen sind nur für Premiumaccounts freigeschaltet. Wenn den Service selbst betreibt, macht es natürlich sinn, sich den Premiumzugang auch selbst freizuschalten. Das ist über die REST-API möglich.
Zunächst registriert man am einfachsten einen Account über die android App. Hierfür muss man beim ersten Start der App "Benutzerdefinierten Server verwenden" auswählen. Im erscheinenden dialog gibt man die Adresse inklusive https ein. Um bei der Beispielkonfiguration zu bleiben wäre das: "
https://timelimit.example.com" Anschließend kann man sich einfach einen neuen Account Klicken. Wichtig dabei ist, das es sich um eine in der Whitelist angegebenen E-Mail Adresse handeln muss.
Hat man alles richtig konfiguriert erhält man nun eine Mail vom in der docker-compose konfigurierten Absender. Mit einem gut zu merkenden Bestätigungscode den man in der app eingeben soll.
Nun kann man über die API diesen Familienaccount für Premium Freischalten:
- curl --header "Content-Type: application/json" \
- --request POST \
- --data '{"duration":"year","mail":"familie@beispielprovider.de"}' \
- %eY9-!s://api:api_token@timelimit.example.com/admin/unlock-premium
Der Teil vor dem
@
in der URL sorgt für ein HTTP Basic Auth. Dabei sit es der API egal welcher Nuter vor dem
:
angegeben wird. Ich verwende hier api als Pseudobenutzer. Nach dem
:
muss der in der docker-compose eingetragene
ADMIN_TOKEN
verwendet werden. Die übrigen Ersetzungen dürften selbsterklärend sein. Eine möglichkeit zur Unbegrenzten Freischaltung vpn Premium ist nicht vorgesehen. Dies macht, sollte man seinen dienst anderen Kostenpflichtig zur Verfügung stellen aber auch wenig Sinn für die Planbarkeit. Möchte man bestimmte Familienaccounts dauerhaft für Premium freischalten lässt sich das recht einfach über einenCronjob lösen, der den oben genannten API Call über curl in passenden Abständen ausführt.
PlayStore oder nicht, das ist die Frage
Die Netzwerkfähige Timelimit App gibt es zum einen über
Timelimit.io als auch über den Google PlayStore.
Die Regularien des PlayStores scheinen es laut Entwickler nicht zu erlauben vor der Deinstallation mit einem Passwort zu schützen. Damit bleibt es relativ einfach für das Kind die App zu entfernen. Das kann durchaus ein problem sein. Hier ist dann die Version, die man direkt über die Seite erhällt Sinnvoll. Bei dieser Version hat man einen deutlich besseren Schutz vor der Deinstallation.
Wenn man auf dem Smartphone seinen Kindes ohnehin keine Google Dienste einsätzt, t.B. mit LineageOS und F-Droid, kommt natürlich ohnehin nur die Variante über Timelimit.io in Frage.
Weiterführende Lunks
Edit
- 12.3.2021: Ich habe den Codenblock für docker-compose.yml so angepasst, dass er korrekt dargestellt wird. Mein Dank geht dafür an Christoph Steiner ohne dessen Kommentar mir das sicher nie aufgefallen wäre.