{ "cells": [ { "cell_type": "markdown", "metadata": { "internals": { "slide_type": "subslide" }, "slideshow": { "slide_type": "slide" } }, "source": [ "# Kinder, Computerzeitbegrenzung und New-Style-Daemonen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tübix 2016\n", "\n", "Anselm Kruis " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Table of Contents\n", "* [Kinder, Computerzeitbegrenzung und New-Style-Daemonen](#Kinder,-Computerzeitbegrenzung-und-New-Style-Daemonen)\n", "\t* [Wer bin ich](#Wer-bin-ich)\n", "* [Einführung](#Einführung)\n", "\t* [Das Problem](#Das-Problem)\n", "\t* [Historie](#Historie)\n", "* [Anwendung](#Anwendung)\n", "\t* [Voraussetzungen](#Voraussetzungen)\n", "\t* [Glib2.48 for Debian Jessie](#Glib2.48-for-Debian-Jessie)\n", "\t* [Installation von ctlimit](#Installation-von-ctlimit)\n", "\t* [Konfiguration von ctlimit](#Konfiguration-von-ctlimit)\n", "\t\t* [Die Kofigurationsdatei /etc/ctlimit.conf](#Die-Kofigurationsdatei-/etc/ctlimit.conf)\n", "\t* [Demo](#Demo)\n", "* [Technischer Teil](#Technischer-Teil)\n", "\t* [Anforderungen](#Anforderungen)\n", "\t* [Wie könnte das funktionieren?](#Wie-könnte-das-funktionieren?)\n", "\t* [Wichtige Komponenten (neben ctlimit)](#Wichtige-Komponenten-%28neben-ctlimit%29)\n", "\t* [Auswahl der Programmiersprache](#Auswahl-der-Programmiersprache)\n", "\t* [New Style Daemon](#New-Style-Daemon)\n", "\t* [Minimales Beispiel - Pure Python](#Minimales-Beispiel---Pure-Python)\n", "\t* [sdnotify - Notify Service Manager](#sdnotify---Notify-Service-Manager)\n", "\t* [Minimales Beispiel - GLib](#Minimales-Beispiel---GLib)\n", "\t\t* [GLib in Python](#GLib-in-Python)\n", "\t\t* [GLib-Mainloop](#GLib-Mainloop)\n", "\t\t* [GLib - wichtigste Funktionen](#GLib---wichtigste-Funktionen)\n", "\t* [Generischer New Style Daemon in Python](#Generischer-New-Style-Daemon-in-Python)\n", "\t* [Konfiguration: [configparser](https://docs.python.org/3/library/configparser.html)](#Konfiguration:-[configparser]%28https://docs.python.org/3/library/configparser.html%29)\n", "\t* [[Python Logging](https://docs.python.org/3/library/logging.html) für New Style Daemons](#[Python-Logging]%28https://docs.python.org/3/library/logging.html%29-für-New-Style-Daemons)\n", "\t* [Das `.service`-File](#Das-.service-File)\n", "\t* [[DBus](https://www.freedesktop.org/wiki/Software/dbus/)](#[DBus]%28https://www.freedesktop.org/wiki/Software/dbus/%29)\n", "\t* [DBus mit Python: pydbus](#DBus-mit-Python:-pydbus)\n", "\t\t* [GIO / pydbus Einschränkungen](#GIO-/-pydbus-Einschränkungen)\n", " * [Beispiel](#Beispiel)\n", " * [DBus-Signale](#DBus-Signale)\n", "\t* [Eigene DBus Services](#Eigene-DBus-Services)\n", "\t\t* [Beispiel: Echo-Server](#Beispiel:-Echo-Server)\n", "\t\t* [System DBus Policy File](#System-DBus-Policy-File)\n", "\t\t* [System DBus Policy File Beispiel](#System-DBus-Policy-File-Beispiel)\n", "\t* [Debugging Tips](#Debugging-Tips)\n", "\t\t* [Remote Debugging mit DBus starten](#Remote-Debugging-mit-DBus-starten)\n" ] }, { "cell_type": "markdown", "metadata": { "internals": { "frag_helper": "fragment_end", "frag_number": 3 }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Wer bin ich" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Diplom Physiker\n", "- Verheiratet, 3 Kinder\n", "- Betruflich seit 2001 bei der science + computing ag in München\n", "- Softwareingenieur, Teamleiter\n", "- Open-Source: ein paar Python Projekte bei github und bitbucket" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Einführung" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Worum geht es?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Das Problem" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Haben sie Lust, mit ihren Kindern über die Dauer der Computer Nutzung zu diskutieren?\n", "\n", "\n", "\n", "Wir - meine Frau und ich - hatten keine." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Das Problem " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ausufernde Computerbenutzung" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Randbedingungen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- gemeinsam genutzter Computer\n", "- Kinder haben haben keine root-Rechte
" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### Lösung" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ein kleines selbstgeschriebenes Programm: **_ctlimit_** beschränkt die Zeit auf faire Weise.\n", "\n", "- Zwangs-Logout sobald die Zeit verbraucht ist \n", "- in Python implementiert\n", "- \"new style daemon\" (sd-daemon(3))\n", "- Beispiel für Linux Desktop Automatisierung, systemd, dbus, GLib ..." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### Keine Lösung für" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Smartphone Nutzung\n", "- eigene Windows Laptops der Kinder" ] }, { "cell_type": "markdown", "metadata": { "internals": { "frag_helper": "fragment_end", "frag_number": 4 }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Historie" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- erste Version um 2010 herum in Java + Shell\n", "- quick and dirty\n", "- funktionierte seit Debian Jessie (logind statt ConsoleKit) nicht mehr\n", "- DBus Bindings für Java werden nicht gepflegt\n", "- Chrisoph Prokop hat mich überredet, hier einen Vortrag zu halten\n", "\n", "Daher:\n", "\n", "- 2016 Neuimplementierung in Python als \n", " [Open-Source Projekt ctlimit](https://bitbucket.org/akruis/ctlimit)" ] }, { "cell_type": "markdown", "metadata": { "internals": { "frag_helper": "fragment_end", "frag_number": 7 }, "slideshow": { "slide_type": "slide" } }, "source": [ "# Anwendung" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Wie nutzt man ctlimit?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "### Installieren" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "### Konfigurieren" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "### Den Computer machen lassen" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "internals": { "frag_helper": "fragment_end", "frag_number": 7, "slide_helper": "subslide_end" }, "slide_helper": "slide_end", "slideshow": { "slide_type": "slide" } }, "source": [ "## Voraussetzungen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Linux Distribution mit\n", "- systemd\n", "- logind\n", "- Graphischer Desktop\n", "- GLib Version >= 2.46\n", " - Ubuntu 16.04 Xenial Xerus\n", " - Fedora Core 23\n", " - OpenSuse TumbleWeed\n", " - Gentoo\n", " - Arch Linux\n", " - für Debian Jessie: Patch" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "subslide" } }, "source": [ "## Glib2.48 for Debian Jessie" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A DBus server with pydbus needs glib >= 2.46. See [1](https://developer.gnome.org/gio/2.46/GDBusConnection.html#g-dbus-connection-register-object-with-closures), [2](https://bugzilla.gnome.org/show_bug.cgi?id=656325).\n", "\n", "[Debian Backports](https://backports.debian.org/) provides [glib 2.48 in jessie-backports](https://packages.debian.org/jessie-backports/libs/libglib2.0-0)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Installation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### libglib 2.48" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Follow the instructions at http://backports.debian.org/Instructions/. Finally run\n", "\n", "```\n", "$ sudo apt-get -t jessie-backports install \\\n", "libglib2.0-0:amd64\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### GI type libraries" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Unfortunately the installing libglib is not sufficient. We need to update the \n", "[GObject introspection](https://wiki.gnome.org/Projects/GObjectIntrospection) \n", "(GI) type libraries for glib. The Debian package is \n", "[gir1.2-glib-2.0](https://packages.debian.org/en/jessie/gir1.2-glib-2.0). \n", "Its source package is [gobject-introspection](https://packages.debian.org/en/source/jessie/gobject-introspection),\n", "which is not part of [jessie-backports](http://packages.debian.org/jessie-backports/).\n", "\n", "Therefore we rebuild it against the new glib 2.48. It does not require root.\n", "\n", "1. Install the dependencies und tools\n", "```\n", "$ sudo apt-get install devscripts\n", "$ sudo apt-get build-dep gobject-introspection\n", "$ mkdir tmpdir && cd tmpdir\n", "$ apt-get source gobject-introspection\n", "$ apt-get source glib2.0/jessie-backports\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2. Save the glib source directory. Without these sources the GI scanner can't \n", " create correct type libraties, because the C-header files don't contain all required information.\n", "```\n", "$ GLIBSRC=$(cd glib2.0-2.48.* && pwd)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "3. Convert the old type library to xml. We need it later to compare the new one against it.\n", "```sh\n", "$ g-ir-generate /usr/lib/x86_64-linux-gnu/girepository-1.0/Gio-2.0.typelib \\\n", ">Gio-2.0_1.42.0-2.2.xml\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "4. Build gobject-introspection\n", "```sh\n", "$ cd gobject-introspection-1.42*\n", "$ dch -i 'Rebuild against glib 2.48 from jessie-backports'\n", "$ DEB_CONFIGURE_EXTRA_FLAGS=\"--with-glib-src=$GLIBSRC\" \\\n", "dpkg-buildpackage -b -us -uc\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "5. Compare the new and the old type library\n", "\n", " ```diff\n", " $ g-ir-generate Gio-2.0.typelib >../Gio-2.0_1.42.0-3.xml\n", "$ cd ..\n", " $ diff -u -w Gio-2.0_1.42.0-2.2.xml Gio-2.0_1.42.0-3.xml | grep register_object\n", " - \n", " + \n", " ```\n", "\n", " If you don't get this output, the build didn't recognise the glib source. The type libraries in the new\n", " package *gir1.2-freedesktop_1.42.0-3_amd64.deb* should be identical to the already installed ones \n", " (from *gir1.2-freedesktop_1.42.0-2.2_amd64.deb*). If your in doubt, extract them (`dpkg-deb -x`) and\n", " compare the files.\n", "\n", "5. Install. You need to install *gir1.2-glib-2.0_1.42.0-3_amd64.deb* and *gir1.2-freedesktop_1.42.0-3_amd64.deb*,\n", " because *gir1.2-freedesktop* depends on the corresponding *gir1.2-glib* package.\n", "```\n", "$ sudo dpkg -i gir1.2-glib-2.0_1.42.0-3_amd64.deb\n", "$ sudo dpkg -i gir1.2-freedesktop_1.42.0-3_amd64.deb\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Installation von ctlimit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wie bei eigentlich allen guten Programmen gilt: \n", "[RTFM](http://pythonhosted.org/ctlimit/)\n", "\n", "1. Pakete der Linux-Distribution installieren. Benötigt werden:\n", " 1. Python ab Version 3.4 mit pip (Debian Paket *python3-pip*)\n", "\n", " 2. Die Kommandos `play` (Debian Paket *sox*), `dbus-send` (*dbus*),\n", " `notify-send` (*libnotify-bin*), `pkg-config` (*pkg-config*).\n", " \n", " 3. Die Python Extension für GObject Introspection (GI) und die GI-Daten für\n", " die Libraries GLib, Gio, Gobject (Debian Pakete *python3-gi* und *gir1.2-glib-2.0*).\n", " \n", "2. Mit `pip` *ctlimit* und die noch verbleibenden Abhängigkeiten installieren\n", " ```\n", " $ sudo pip3 install ctlimit\n", " ```\n", "\n", "3. Einrichten\n", " ```\n", " $ sudo python3 -m ctlimit -c /dev/null --install-system-files\n", " ```" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "## Konfiguration von ctlimit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alles als root bzw. mit `sudo`.\n", "\n", "1. Konfigurationsdatei editieren: `$EDITOR /etc/ctlimit.conf`
\n", " Danach `systemctl reload ctlimit.service`\n", "\n", "2. ctlimit starten: `systemctl start ctlimit.service`\n", "\n", "3. Kontrolle: `journalctl _SYSTEMD_UNIT=ctlimit.service`\n", "\n", "4. ctlimit automatisch starten: `systemctl enable ctlimit.service`" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Die Kofigurationsdatei /etc/ctlimit.conf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "INI-File bzw Python Configparser [Syntax](https://docs.python.org/3/library/configparser.html#supported-ini-file-structure).\n", "\n", "Minimales Beispiel:\n", "```\n", "[DEFAULT]\n", "seconds_per_day: 3600\n", "\n", "[Users]\n", "users = bob alice\n", "\n", "[alice]\n", "name = qx471193\n", "seconds_per_day: 5400\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "User `bob` hat eine Stunde, User `qx471193` hat 1,5 Stunden." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Demo" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Zeit für eine Demonstration\n", "\n", "- Benutzer \"kind\" in die Konfiguration eintragen\n", "- Benutzer anmelden\n", "- Abwarten" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Technischer Teil" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Warum Daemonen Entwicklung einfach ist" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Anforderungen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Technisch saubere Umsetzung als New Style Daemon\n", "2. Möglichst geringer Resourcenverbrauch \n", "3. Nur echte Nutzung erfassen:\n", " + Uhr steht, wenn die virtuelle Console nicht aktiv ist\n", " + Uhr steht, wenn die Session idle ist (z.B. Bildschirmschoner aktiv)\n", "4. Bei Zeitablauf Abmeldung mit Ankündigung\n", " + z.B. optisches und akustisches Signal für 5 Minuten\n", "5. Nutzungsstatus muss einen Reboot überstehen\n", "6. Konfigurierbarkeit\n", " + zu überwachende User\n", " + erlaubte Nutzungsdauer pro Tag\n", " \n", " *Nice to have:*\n", "7. Möglichkeit ctlimit zu beeinflussen, z.B. zusätzliche Zeit zu gewähren\n", "8. Abfrage der aktuellen Nutzungsdauer" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Wie könnte das funktionieren?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Beobachtung: automatische Aktivierung des Bildschirmschoners\n", "\n", "==> Desktop hat offensichtlich eine Idle Überwachung" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Beobachtung: Bildschirmsperre beim Umschaltung virtueller Konsolen\n", "\n", "==> auch hier weiß der Computer, welche aktiv ist\n", "\n", "Wenn wir diese Informationen anzapfen können,\n", "dann läßt sich eine Zeitbeschränkung einfach realisieren." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### Wichtigste Infoquelle" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "https://www.freedesktop.org/" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Wichtige Komponenten (neben ctlimit)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [Systemd](https://www.freedesktop.org/wiki/Software/systemd/)\n", " - Das heutige *init* (PID 1)\n", " - Startet und kontrolliert Services, u.a. logind, display manager, system-dbus, ctlimit" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- [Logind](https://www.freedesktop.org/wiki/Software/systemd/logind/) (oder [ConsoleKit](https://www.freedesktop.org/wiki/Software/ConsoleKit/))\n", " - Verwaltet Sessions\n", " - Kennt Session Properties, insbesondere User, Display, Idle-State " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- Screen Saver / Screen Locker\n", " - Erkennt Idle Zustand, meldet ihn weiter an logind (oder ConsoleKit)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- [DBus](https://dbus.freedesktop.org/doc/dbus-tutorial.html)\n", " - Ermöglicht die Kommunikation der Komponenten\n", " - System-Bus und je ein Session-Bus pro Session\n", " - ctlimit nutzt den System-Bus\n", " + um Informationen vom Logind zu bekommen\n", " + zur Kontrolle von ctlimit" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Auswahl der Programmiersprache" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Oder warum gerade Python? Warum nicht C++, C, Perl, Java, Ruby, Lua, eLisp, Shell, ..." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- Ich kann es gut\n", "- Es gibt alle nötigen Bindings\n", "- Es gibt gute IDEs\n", "- Die Sprache ist einfach und gut dokumentiert" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "### Warum GLib / Gio / GObject" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Die DBus Anbindung [pydbus]() benötigt GLib und Gio\n", "- Gute Unterstützung für \"systemnahe\" Linux Programme\n", "- Einfach anwendbar\n", "- Sehr mächtig" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## New Style Daemon" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wird auch **sd-daemon** genannt.\n", "\n", "- Dokumentiert in [daemon(7)](https://www.freedesktop.org/software/systemd/man/daemon.html) und [sd-daemon(3)](https://www.freedesktop.org/software/systemd/man/sd-daemon.html)\n", "- Normaler Unix-Prozess, der einige Konventionen einhält\n", "- Beschreibung durch eine [systemd.service(5)](https://www.freedesktop.org/software/systemd/man/systemd.service.html)-Datei in `/lib/systemd/system/`\n", "- Prozess erfordert keine besondere Initialisierung\n", " + Keine UNIX SysV Daemon-Initialisierung nötig\n", "- Am Ende der Initialisierung Ready-Meldung mit [sd_notify(3)](https://www.freedesktop.org/software/systemd/man/sd_notify.html)\n", "- SIGTERM beendet Prozess\n", "- SIGHUP lädt die Konfiguration neu\n", "- Logging: Ausgabe auf STDERR, Optional `\"<`*LEVEL*`>\"` am Zeilenanfang" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "### Vergleich zum klassischen SysV-Daemon" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Zwei forks und umfangreiche Initialisierung aller Prozesseigenschaften nötig\n", "- Laut [daemon(7)](https://www.freedesktop.org/software/systemd/man/daemon.html) benötigt man 15 Schritte, um einen Daemon zu erzeugen\n", "- [PEP 3143 -- Standard daemon process library](https://www.python.org/dev/peps/pep-3143/), \n", " Implementierung bei PyPi [python-daemon](https://pypi.python.org/pypi/python-daemon)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Minimales Beispiel - Pure Python" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import sys, os, signal, selectors, sdnotify\n", "signal_received = {}\n", "systemd_notifier = sdnotify.SystemdNotifier()\n", "sel = selectors.DefaultSelector()\n", "\n", "def _on_signal(signalnum, frame):\n", " signal_received[signalnum] = True\n", " raise InterruptedError\n", "for s in (signal.SIGTERM, signal.SIGHUP): signal.signal(s, _on_signal)\n", "\n", "# additional setup code, i.e. sel.register(sock, selectors.EVENT_READ, accept)\n", "\n", "print(\"<6> Starting, PID is \", os.getpid(), file=sys.stderr)\n", "systemd_notifier.notify(\"READY=1\")\n", "\n", "while signal.SIGTERM not in signal_received:\n", " if signal_received.pop(signal.SIGHUP, False):\n", " systemd_notifier.notify(\"RELOADING=1\")\n", " print(\"<6> reconfig\", file=sys.stderr)\n", " systemd_notifier.notify(\"READY=1\")\n", " # regular events\n", " for key, mask in sel.select(): key.data(key.fileobj, mask)\n", "\n", "systemd_notifier.notify(\"STOPPING=1\")\n", "print(\"<6> Terminated\", file=sys.stderr)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### sdnotify - Notify Service Manager" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Neue C-Funktion [sd_notify(3)](https://www.freedesktop.org/software/systemd/man/sd_notify.html): Notify service manager (systemd) about start-up completion and other service status changes.\n", "\n", "Python Implementierung: Module [sdnotify](https://pypi.python.org/pypi/sdnotify).\n", "\n", "- Der systemd muss erkennen können, wann ein gestarteter Service betriebsbereit ist\n", "- Service schickt Nachricht `\"READY=1\"`\n", "- Weitere Nachrichten sind optional\n", " + bei Reload der Konfiguation\n", " + bei Beendigung\n", " + und ander mehr\n", "- Praktisch: `sdnotify.SystemdNotifier().notify(\"`...`\")` ignoriert Sende-Fehler\n", " + Programm kann auch ohne systemd gestartet werden" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### Vergleich mit SysV Daemon" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Betriebsbereitschaft nach erfolgreicher Beendigung des 1. Prozesses." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Minimales Beispiel - GLib" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "format": "row", "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "import sys, os, sdnotify\n", "from signal import SIGHUP, SIGTERM\n", "from gi.repository import GLib\n", "mainloop = GLib.MainLoop()\n", "systemd_notifier = sdnotify.SystemdNotifier()\n", "\n", "def _initialize(is_reconfig=False):\n", " if is_reconfig: \n", " systemd_notifier.notify(\"RELOADING=1\")\n", " print(\"<6> initializing, is_reconfig:\", is_reconfig, file=sys.stderr)\n", " systemd_notifier.notify(\"READY=1\")\n", " return is_reconfig\n", "\n", "GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, SIGHUP, _initialize, True)\n", "GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, SIGTERM, mainloop.quit)\n", "GLib.idle_add(_initialize)\n", "print(\"<6> Starting, PID is \", os.getpid(), file=sys.stderr)\n", "mainloop.run()\n", "systemd_notifier.notify(\"STOPPING=1\")\n", "print(\"<6> Terminated\", file=sys.stderr)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### GLib in Python" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Wikipedia](https://de.wikipedia.org/wiki/GLib): \n", "> GLib ist eine in C geschriebene Bibliothek, die verschiedene Funktionen bereitstellt, die normalerweise in C nur mit sehr viel Aufwand möglich sind.\n", "\n", "Bibliotheken\n", "- [GLib](http://developer.gnome.org/glib/stable/): allgemeine Funktionen\n", "- [GObject](http://library.gnome.org/devel/gobject/stable/): Objektsystem, Signalsystem, Nachrichten\n", " + In Python nicht so wichtiug, Python kann das selbst\n", "- [GIO (GLib Interfaces and Objects)](http://library.gnome.org/devel/gio/stable/): Datei- und Datenstromobjekte, Netzwerkfunktionalität, Interprozess-Kommunikationssystem D-Bus, ..." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### Python Binding: GObject Introspection" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Bibliotheken haben eine maschinenlesbare Schnittstellenbeschreibung (`.typelib`)\n", " + [g-ir-scanner(1)](http://man.cx/g-ir-scanner%281%29) erzeugt GIR-XML Daten aus Source und Header Dateien\n", " + [g-ir-compiler(1)](http://man.cx/g-ir-compiler%281%29) erzeugt typelib aus GIR-XML. \n", " + typelib-Repository: `/usr/lib/x86_64-linux-gnu/girepository-1.0/*.typelib`\n", "- Python Extension Module [gi](https://wiki.gnome.org/Projects/PyGObject) ließt `.typelib` und \n", " erzeugt dynamisch das Python-Binding\n", "- Funktioniert inzwischen für viele weitere Libraries: Ubuntu hat 202 `gir1.2-.*\\.deb`-Pakete" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### GLib-Mainloop" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Typischer Programmaufbau" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "from gi.repository import GLib\n", "# create the mainloop\n", "mainloop = GLib.MainLoop()\n", "\n", "# define a callback\n", "counter = 0\n", "def count_down(limit):\n", " global counter\n", " counter += 1\n", " print(limit - counter, flush=True)\n", " return counter != limit # run until 0\n", "# add callbacks\n", "GLib.timeout_add_seconds(1, count_down, 5)\n", "GLib.timeout_add_seconds(6, mainloop.quit) # terminate the loop\n", "# run it\n", "mainloop.run()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### GLib - wichtigste Funktionen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [`mainloop.run()`](https://lazka.github.io/pgi-docs/GLib-2.0/structs/MainLoop.html#GLib.MainLoop.run): startet die Mainloop\n", "- [`mainloop.quit()`](https://lazka.github.io/pgi-docs/GLib-2.0/structs/MainLoop.html#GLib.MainLoop.quit): beendet die Mainloop\n", "- [`GLib.idle_add()`](https://lazka.github.io/pgi-docs/GLib-2.0/functions.html#GLib.idle_add): Callback wenn idle\n", "- [`GLib.timeout_add()`](https://lazka.github.io/pgi-docs/GLib-2.0/functions.html#GLib.timeout_add): periodischer Callback\n", "- [`GLib.timeout_add_seconds()`](https://lazka.github.io/pgi-docs/GLib-2.0/functions.html#GLib.timeout_add_seconds):\n", " periodischer callback\n", "- [`GLib.unix_signal_add()`](https://lazka.github.io/pgi-docs/GLib-2.0/functions.html#GLib.unix_signal_add): Unix Signal Callback\n", "\n", "Doku für GI-basierte Python Bindings\n", "https://lazka.github.io/pgi-docs/index.html" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Generischer New Style Daemon in Python" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Den bisher gezeigten Beispielen fehlt noch einiges.\n", "- Aktivierung durch Ereignisse und Timer\n", "- Konfiguation\n", "- Logging" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### Modul ctlimit.sddaemon" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Abstrakte Basisklasse `EventDrivenDaemon`
\n", " zuständig für\n", " - Das sd-daemon Protokoll\n", " - Aktivierung durch Events\n", " - Konfiguration und Persistentz \n", "- Klasse SdDaemonLoggingFormatter\n", " - Python-Logging Ausgabe im sd-daemon Format" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Konfiguration: [configparser](https://docs.python.org/3/library/configparser.html)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Parser für INI-Files. Seit Python 3.2 recht gut brauchbar.\n", "\n", "Erwähnenswert:\n", "- [ExtendedInterpolation](https://docs.python.org/3/library/configparser.html#configparser.ExtendedInterpolation) verwenden.\n", " - Tip: Kommandozeilen Optionen und Environment programmatisch in Sections packen\n", " - Interpolation erlaubt dann sehr flexibles Verhalten\n", "- Mehrere Konfigurationsdateien in einen configparser einlesen\n", " + Grundeinstellungen im Python Package Directory `ctlimit_default.conf`\n", " + User-Konfiguration in `/etc/ctlimit.conf`\n", " + Zustandsinformation `/var/lib/ctlimit/ctlimit.state`\n", "- Sections mit Zustand speichern in `/var/lib/ctlimit/ctlimit.state`\n", "- Logging wird mit dem selben configparser-Object konfiguriert" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## [Python Logging](https://docs.python.org/3/library/logging.html) für New Style Daemons" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Spezielle Konfiguaration zur Ausgabe der Meldungen mit Prefix auf STDERR.\n", "- [Handler](https://docs.python.org/3/library/logging.html#handler-objects): \n", " [logging.StreamHandler](https://docs.python.org/3/library/logging.handlers.html#logging.StreamHandler)\n", "- [Formatter](https://docs.python.org/3/library/logging.html#formatter-objects): eigene Klasse `ctlimit.SdDaemonLoggingFormatter`.\n", "\n", "Logging Konfiguration im [configparser-Format](https://docs.python.org/3/library/logging.config.html#configuration-file-format):\n", "```ini\n", "[loggers]\n", "keys=root\n", "[handlers]\n", "keys=sd_daemon\n", "[formatters]\n", "keys=sd_daemon\n", "[logger_root]\n", "level=${common:log_level}\n", "handlers=sd_daemon\n", "[handler_sd_daemon]\n", "class=StreamHandler\n", "level=DEBUG\n", "formatter = sd_daemon\n", "args=(sys.stderr,)\n", "[formatter_sd_daemon]\n", "format=%(levelname)s: %(message)s\n", "class=ctlimit.SdDaemonLoggingFormatter\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Weitergehende Infos: https://www.loggly.com/blog/logging-in-new-style-daemons-with-systemd/" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Das `.service`-File" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Macht aus einem Programm einen systemd-service. \n", "Dokumentation: [systemd.service(5)](https://www.freedesktop.org/software/systemd/man/systemd.service.html) \n", "und [systemd.directives(7)](https://www.freedesktop.org/software/systemd/man/systemd.directives.html)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Beispiel: `/lib/systemd/system/ctlimit.service`\n", "```INI\n", "[Unit]\n", "Description=ctlimit computer time limitation service\n", "Documentation=https://doc.for.ctlimit\n", "\n", "[Service]\n", "Type=notify\n", "NotifyAccess=main\n", "\n", "ExecStart=/usr/bin/env python3 -m ctlimit\n", "ExecReload=/bin/kill -HUP $MAINPID\n", "KillMode=mixed\n", "TimeoutStopSec=5\n", "\n", "SyslogIdentifier=ctlimit\n", "StandardOutput=syslog\n", "StandardError=inherit\n", "\n", "[Install]\n", "WantedBy=multi-user.target\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## [DBus](https://www.freedesktop.org/wiki/Software/dbus/)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Basics\n", "- Erlaubt Nachrichtenaustausch zwischen Programmen\n", " + Aufruf von Methoden, Abfrage von Properties\n", " + Empfang von Signalen\n", " + Interfaces als Namensräume\n", "- Verwaltet Namen und Objekt-Pfade\n", "- Kann Server automatisch starten\n", "- Policy regelt, welche Nachrichten transportiert werden" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "### Entwicklungstools DBus" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [dbus-send](): Kommandozeilen-Tool\n", "- [d-feet](): Graphisches Pondon zu `dbus-send`\n", "- [dbus-monitor](): Tool zum Sniffen\n", "\n", "Achtung: zum Sniffen am System DBus müssen die Sicherheitseinstellungen gelockert werden.\n", "- Weitergehende Hinweise: https://wiki.ubuntu.com/DebuggingDBus\n", "- Zum Testen: https://www.piware.de/2012/09/announcing-d-bus-mocker-library/" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## DBus mit Python: pydbus" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[pydbus](https://pypi.python.org/pypi/pydbus): high level pythonic DBus library based on GIO.\n", "- sehr einfache Anwendung\n", "- noch recht jung\n", "- Könnte die Zukunft der DBus-Programmierung sein" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### Alternativen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Siehe https://wiki.python.org/moin/DbusExamples\n", "\n", "- [dbus-python](https://dbus.freedesktop.org/doc/dbus-python/): der Klassiker\n", "- [txdbus](https://pypi.python.org/pypi/txdbus): DBus für das Twisted Framework (Pure-Python)\n", "- [GIO](https://developer.gnome.org/gio/stable/gdbus-convenience.html): da kann man gleich pydbus nehmen" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### Probleme" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Asynchrone Arbeitsweise (Standard bei GLib/GIO) nicht unterstützt\n", "- Teil der GIO DBus Funktionalität ist nicht nutzbar" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### GIO / pydbus Einschränkungen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- GIO Klasse [GDBusObjectManagerServer](https://developer.gnome.org/gio/stable/GDBusObjectManagerServer.html) ist \n", " nicht introspection friendly.\n", " - Problem ist die Interface Funktion\n", " [g_dbus_interface_skeleton_get_vtable()](https://developer.gnome.org/gio/2.48/GDBusInterfaceSkeleton.html#g-dbus-interface-skeleton-get-vtable)\n", " - Folge: Standard-Interface [org.freedesktop.DBus.ObjectManager]() kann nicht verwendet werden\n", "- pydbus arbeitet synchron. Die asynchrone Arbeitsweise der GLib / GIO wird nicht unterstützt\n", "- Erstellung eigener Services mit pydbus ist noch unausgereift\n", " - Authentisierung des Aufrufers nicht möglich\n", " - pydbus bietet noch keine [Policy-Kit](https://www.freedesktop.org/wiki/Software/polkit/) Unterstützung" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Beispiel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alle Sessions für den Benutzer \"anselm\" ausgeben" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "import pydbus\n", "bus = pydbus.SystemBus()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "manager = bus.get(\".login1\")[\".Manager\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "scrolled": true, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "for session_infos in manager.ListSessions():\n", " session = bus.get(\".login1\", session_infos[4])\n", " print(session.Id, \" User:\", session.Name, \" Type:\", session.Type,\n", " \" State:\", session.State,\" Service:\", session.Service)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "session.Name\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### DBus-Signale" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- sind asynchron eintreffende Nachrichten (Events) auf dem DBus\n", "- werden mit einem Signal-Handler-Funktion bearbeitet\n", "- Beispiel:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "from gi.repository import GLib\n", "mainloop = GLib.MainLoop()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "manager = bus.get(\".login1\")[\".Manager\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "manager.onSessionNew = print\n", "GLib.timeout_add_seconds(30, mainloop.quit)\n", "mainloop.run()\n", "manager.onSessionNew = None" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "DBus-Signale ermöglichen es, auf externe Ereignisse zu reagieren." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Eigene DBus Services" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Das Kind war brav und soll ein Bisschen zusätzliche Zeit bekommen.\n", "Was tun?\n", "- Konfigurationsdatei /etc/ctlimit.conf editieren\n", "- systemctl reload ctlimit.service\n", "- Vergessen die Verlängerung zurückzunehmen" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Oder:\n", "```\n", "$ dbus-send --system --type=method_call --print-reply \\\n", "> --dest=de.kruis.ctlimit1 \"/de/kruis/ctlimit1/User$(id -u kind)\" \\\n", "> de.kruis.ctlimit1.User.IncreaseCurrentLimit \\\n", "> int32:1800\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(Das lässt sich auch gut vom Smatphone aus machen.)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Beispiel: Echo-Server" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "from pydbus import SessionBus\n", "from gi.repository import GLib\n", "class EchoServer:\n", " \"\"\"\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \"\"\"\n", " def Echo(self, arg):\n", " print(\"Echo(%r)\" % arg, flush=True)\n", " return arg\n", " def Quit(self):\n", " self.mainloop.quit()\n", " def run(self):\n", " self.mainloop = GLib.MainLoop()\n", " with SessionBus().publish(\"de.kruis.demo\", self):\n", " self.mainloop.run()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "EchoServer().run()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### System DBus Policy File" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- `/etc/dbus-1/system.d/`*Service-Name*`.conf`\n", "- Konfiguration der welche Nachrichten auf dem Bus erlaubt sind.\n", "- Dokumentiert in [dbus-daemon(1)](https://dbus.freedesktop.org/doc/dbus-daemon.1.html)\n", "- Das KDE Projekt hat ein [Tutorial](https://techbase.kde.org/Development/Tutorials/PolicyKit/Helper_HowTo#The_DBus_policy_file)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### System DBus Policy File Beispiel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```xml\n", "\n", " \n", " \n", " \n", " \n", " \t\n", " \n", "\n", " \n", " ... \n", " \n", " \n", " \n", " \n", " \n", " ...\n", " \n", " \n", " \n", "\t\n", "\t\n", "\t\n", " \n", "\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Debugging Tips" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- [pdb](https://docs.python.org/3/library/pdb.html) ist nicht optimal\n", "- Es gibt eine [IDE Liste](https://wiki.python.org/moin/IntegratedDevelopmentEnvironments)\n", "- Ich verwende [PyDev](http://www.pydev.org/)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### Remote Debugging" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fast immer wenn es in der IDE nicht mehr weitergeht hilft Remote Debugging.\n", "- Geeigneter Debugger [Pydev.Debugger](https://github.com/fabioz/PyDev.Debugger), enthalten in\n", " Eclipse [Pydev](http://www.pydev.org/), \n", " [LiClipse](http://www.liclipse.com/) und [PyCharm](https://www.jetbrains.com/pycharm/)\n", "- Modul [pydevd](https://pypi.python.org/pypi/pydevd) verfügbar machen, z.B.
\n", " `pip install pydevd`\n", "- An Debugger verbinden: `import pydevd; pydevd.settrace(...)`" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "#### DBus" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Beim Entwickeln / Coden möglichst den Session Bus verwenden\n", "- Alternativ einen eigenen DBus starten:
\n", " `dbus-run-session [--config-file FILENAME] PROGRAM [ARGUMENTS...]`\n", "- Tools: dbus-monitor(1), dbus-send(1), d-feet(1)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Remote Debugging mit DBus starten" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Über den System-DBus aufrufbare Methode\n", "```xml\n", " ...\n", " \n", " \n", " \n", " \n", " ...\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "def AttachPydevd(self, path, kwargs_json):\n", " if path and path not in sys.path:\n", " sys.path.insert(0, path)\n", "\n", " import pydevd\n", " kwargs = json.loads(kwargs_json or \"{}\")\n", " pydevd.settrace(**kwargs)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Aufruf\n", "```sh\n", "$ PYDEVD_DIR=$(find $(dirname $(which eclipse)) -type d -name pysrc)\n", "$ sudo dbus-send --system --type=method_call \\\n", "> --dest=de.kruis.ctlimit1 \"/de/kruis/ctlimit1\" \\\n", "> de.kruis.ctlimit1.Daemon.AttachPydevd \\\n", "> string:\"$PYDEVD_DIR\" \\\n", "> string:'{\"stderrToServer\": true, \"stdoutToServer\": true}'\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Fragen?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "und Antworten" ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.2" }, "widgets": { "state": {}, "version": "1.1.2" } }, "nbformat": 4, "nbformat_minor": 0 }