Scuttlebutt“ ist ein dezentrales und autonomes soziales Netzwerk. Dieses möchte ich hier vorstellen. Dafür werde ich zuerst ein paar Begriffe erklären, darauffolgend das Protokoll vorstellen und zum Schluss einige Anwendungen zeigen, die es gibt.

Begriffe

„soziales Netzwerk“ — „dezentral“ — „autonom“ — „Scuttlebut“

Ein soziales Netzwerk kann als eine Assoziation von Menschen verstanden werden, die auf verschiedene Arten miteinander kommunizieren. Es ist wichtig anzumerken, dass in einem sozialen Netzwerk die Menschen im Fokus stehen und weniger die Kommunikationsweise selbst.

Dezentral heißt hier, dass es keine zentrale Autorität gibt, die bestimmt welche Inhalte für das Netzwerk relevant sind.

Autonom bedeutet, dass die Software auch ohne Internet funktioniert, da die Benutzer direkt miteinander kommunizieren können, zum Beispiel über Freifunk, Bluetooth oder ein lokales Netzwerk.

Der Begriff „Scuttlebutt“ kommt aus der englischen Seemannssprache. Ein „Scuttlebutt“ ist eine Tonne mit Trinkwasser, an der sich Seeleute bedient haben. Da diese Tonne ein Treffpunkt für die Seeleute war, wurden dort auch Informationen ausgetauscht. Das soziale Netzwerk „Scuttlebutt“ ist im übertragenen Sinn die Wassertonne an welcher sich die Menschen treffen und ihre Neuigkeiten austauschen.

Protokoll

In erster Linie ist „Scuttlebutt“ ein Protokoll. Ein Protokoll ist ein definierter Satz von Regeln, die die Kommunikation zwischen zwei Maschinen festlegt.

Dieses basiert auf dem „Gossip Protocol“. Eine vereinfachte Erläuterung geht so: In einem fiktiven Büro trifft Anja Felix in der Küche und tratscht, dass Tim sehr verwildert aussieht, weil er seit Tagen seinen Bart nicht gepflegt hat. Etwas später trifft Felix Wilma und Isa in der Küche und tratscht weiter, dass Tim seinen Bart nicht pflegt. So haben Wilma und Isa die Information erhalten, obwohl sie nicht direkt mit Anja geredet haben. Bei „Scuttlebutt“ läuft das ähnlich, nur wird mit „Kryptographie“ sichergestellt, dass die Informationen von Anja auch wirklich von ihr stammen. Da die Rechner der meisten Personen im Internet nicht von anderen Rechnern erreichbar sind, können die Rechner nicht direkt miteinander kommunizieren, sondern brauchen einen Mittelsmann. Im „Scuttlebutt“-Universum nennt man diesen gemeinsamen Treffpunkt den „Pub“, englisch für Kneipe. Die Kneipen werden von Individuen getragen, so betreibe ich auch eine Kneipe unter pub.datenknoten.

Meistens werden Protokolle in Bibliotheken programmiert, damit Anwendungen diese Bibliotheken benutzen können und jedes Programm das komplette Protokoll implementieren muss. Implementierungen des Protokolls sind die Bibliotheken scuttlebot für JavaScript-Projekte oder auch ssb-client-rs für Rust.

Soziales Netzwerk

Ich habe bisher ja eher den technischen Bereich beschrieben, wieso soll das ganze jetzt ein soziales Netzwerk sein? Im Kern von Scuttlebutt kann man Nachrichten und Dateien austauschen. Es gibt jetzt Programme wie Patchwork oder auch Patchbay die bestimmte Nachrichten-Typen verarbeiten. Der aktuelle Konsens sind folgende Typen:

  • post
  • about
  • contact
  • vote

post ist ein Artikel, der aus Markdown besteht und in dem man auch andere Nachrichten oder Dateien verlinken kann. Eine post-Nachricht kann auch ein Kommentar auf einen anderen Artikel sein.

Der about Nachrichten-Typ erlaubt es hier, sich selbst zu beschreiben. Man kann hier einen Namen, ein Bild oder einen Beschreibungstext hinterlegen.

Mit contact deutet ein Mensch an, ob er einem anderen Konto folgt oder blockiert.

Zum Schluss gibt es noch vote. Mit diesem Nachrichten-Typ kann man andere Nachrichten beurteilen. Der Wert +1 ist um Zustimmung zu einem zu bekunden. Mit dem Wert 0 kann man ein vorher gemachte +1 Abstimmung rückgängig machen. Der Wert -1 wird momentan nicht genutzt, es ist geplant damit Nachrichten zu markieren, z.B. das es sich um Spam oder unsachgemäße Kommunikation handelt.

Ein Programm kann seinen eigenen Nachrichten-Typ implementieren, dieser wird auch durch das Protokoll an andere Benutzer weitergereicht. Wenn das Programm des Benutzers damit nichts anfangen kann, dann wird die Nachricht nicht angezeigt.

Fazit

Insgesamt finde ich “Scuttlebutt” als Medium zur medialen Vernetzung sehr interessant, weil die Grundidee dem realweltlichen Sozialverhalten anlehnt. Auch auf der technischen Entwicklungsebene beobachte ich Veränderungen und bin sehr gespannt, was die nächste Zeit so bringen wird.

Viel Spaß mit „Scuttlebut“!

Am 2018-03-15 wurde der erste „release candidate“ von TypeScript 2.8 veröffentlicht.

Für mich sind folgende Features relevant:

  • Conditional Types
  • Granular Control on Mapped Type Modifiers

Conditional Types

Hiermit kann ich den Typen eines Ausdrucks zur Compile-Zeit dynamisch bestimmen. Benutzt werden dazu der ternären Auswahloperator ?:.

Hier ein Beispiel aus dem oben erwähnten Artikel:

123456789
type IdOrName<T extends number | string> =
    T extends number ? Id : Name;

declare function createLabel<T extends number | string>(idOrName: T): IdOrName<T>;

let a = createLabel("tim");         // Name
let b = createLabel(42);            // Id
let c = createLabel("" as any);     // Id | Name
let d = createLabel("" as never);   // never

Zuerst wird der Typ IdOrName definiert, der einen Typparameter T extends number | string hat, dieser besagt das T entweder ein String oder eine Zahl sein kann und definiert sich dann über den „Conditional Type“. Wenn T vom Typen number ist, dann ist der Typ IdOrName<T> vom Typen Id in fast allen anderen Fällen vom Typen Name. Als nächstes wird die Funktion createLabel erzeugt um ein paar Beispiele zu zeigen. Die Funktion hat einen Typparameter T der nur number oder string sein kann und benutzt diesen im Parameter idOrName über den sich dann auch der Rückgabetyp IdOrName<T> ergibt. Gibt man den Wert 42 für den Parameter idOrName rein, so ist der Rückgabetyp den effektiven Typen Id. Für die Werte "tim", [] oder auch {} würde der effektive Typ Name zurück gegeben werden. Es gibt 2 Sonderfälle, zum einen der Typ any, der alle Werte annehmen kann, entsprechend ist hier der effektive Typ auch Id | Name, das heist es ist der Typ Id oder Name. Zum anderen gibt es noch den Typen never, dort wird der effektive Typ von IdOrName<T> zu never.

Granular Control on Mapped Type Modifiers

Als zweites neue Feature gibt es „Granular Control on Mapped Type Modifiers“. Damit kann man die readonly Eigenschaft und die undefined-Spezifität in Typen ändern die einen Index-Accessor für das Objekt bereitstellen. Hier das Beispiel aus dem original Blog-Artikel:

 1 2 3 4 5 6 7 8 9101112
interface Foo {
    readonly abc: number;
    def?: string;
}

type Props<T> = {
    [P in keyof T]: T[P]
}

// All modifiers are copied over.
// 'abc' is read-only, and 'def' is optional.
type IdenticalFoo = Props<Foo>

Wir haben hier das Interface Foo das die readonly-Eigenschaft abc und die optionale Eigenschaft def. Jetzt definieren wir den Typen Props mit den generischen Parameter T der einen Index-Access bereitstellt. Als Index sind nur Attribute des Typen T erlaubt und der Accessor gibt auch die Werte einfach so zurück. Aus diesem Grund verhällt sich der Typ IdenticalFoo genau so wie das Interface Foo. Soweit so gut, jetzt die Neuerung:

 1 2 3 4 5 6 7 8 91011
type Mutable<T> = {
    -readonly [P in keyof T]: T[P]
}

interface Foo {
    readonly abc: number;
    def?: string;
}

// 'abc' is no longer read-only, but 'def' is still optional.
type TotallyMutableFoo = Mutable<Foo>

Durch die Angabe von -readonly beim Typen Mutable wird nun die Eigenschaft readonly des Interfaces ignoriert. Auch hinzugekommen ist, das man optionale Felder zu erforderlichen machen kann:

123456
/**
 * Make all properties in T required
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
}

Neben dem entfernen der Eigenschaften von Attributen, kann man sie auch wieder hinzufügen:

123456
/**
 * Make all properties in T optional
 */
type OptionalReadonly<T> = {
    +readonly [P in keyof T]+?: T[P];
}

Fazit

Es gibt noch ein paar mehr Änderungen im Bereich JSX, da ich das aktuell nicht benutze hat das für mich keine Relevanz. Das könnte sich mit Stencil allerdings ändern.

Insgesamt bin ich sehr zufrieden mit der Entwicklung von TypeScript weiterhin sehr zufrieden.

Gestern, der 2018-03-17, wurde Gajim 1.0 veröffentlicht. Diese Version befand sich 5 Jahre in der Entwicklung und das Hauptaugenmerk war die Wexel von Python 2 auf 3 und GTK 2 auf 3. Vom äußerlichen hat sich nicht sooo viel geändert, aber ich hoffe mal das hier als nächstes die Hand angelegt wird, den schön ist Gajim immer noch nicht so richtig, aber es ist der beste und wichtigeste XMPP-Client den es zur Zeit auf dem Desktop-Markt gibt. Eine Entscheidung die ich als wichtig und gut empfinde, das das OTR-Plugin nicht portiert wurde und stattdessen der Fokus auf OMEMO gelegt wurde.

Ich wünsche dem Gajim-Projekt viel Erfolg auf ihrem Weg zu einem der besten XMPP-Clients die es da draußen gibt.

Ein guter Freund von mir berichtete, dass er seit einem Docker-Update einen PostgreSQL-Container nicht mehr starten konnte, ihn deshalb löschen muste und dann seine Daten verloren gingen. Datenverlust ist nie lustig und aus diesem Grund will ich dem geneigten Leser einige Erfahrungen auf den Weg geben, die ich in den letzten anderthalb Jahren bei meinem Arbeitgeber sammeln konnte.

Container sind zustandslos und unveränderbar

Was heißt „zustandslos“?

Zwar verhällt es sich nicht immer so, dass Container zustandtslos und unveränderbar sind, aber man sollte sie dennoch so betrachten. Damit wird dann die Frage aufgeworfen, wenn der Zustand der Anwendung nicht im Container gespeichert wird, wo wird er dann gespeichert? Das Handbuch gibt hier zwei Möglichkeiten vor:

  • Volumes
  • Bind-mount

docker volumes

Volumes sind laut Dokumentation der präferierte Weg den Zustand eines Containers zu persistieren. Meine persönliche Erfahrung ist eine andere. Ich benutze lieber bind mounts, da hier keine Verwaltung von nöten ist. Volumes kann ich auch wesentlich einfacher aus Versehen löschen, ein Ordner im Dateisystem erfordert mehr Aufwand.

bind mounts

Mittels eines „bind mounts“ kann ich auf einem Linux-System einen Ordner auf einen anderen Ordner zeigen lassen, so das beide über den selben Inhalt verfügen. So kann ein Ordner auf dem Hostsystem und in dem Docker-Container den selben Inhalt besitzen. Dadurch wird der Zustand des Containers außerhalb des Containers gespeichert und man kann ihn mit seinen regulären Werkzeugen sichern und beobachten.

Was heißt „unveränderbar“?

Nachdem ich nun den Zustand der Datenbank außerhalb des Containers speichere, gilt es die zweite Eigenschaft „unveränderbar“ (engl immuteable) zu betrachten. Es ist zwar möglich einen Container zu bearbeiten, aber spätestens wenn man diesen Container löscht oder einen zweiten anlegt, wird sich das rächen, da die Änderung weg ist. Willst du wirklich eine Änderung an einem Container vornehmen, empfiehlt es sich, das dem Container zu grunde liegende Abbild zu modifizieren und in ein eigenes abzuspeichern.

Aber ich kann doch nicht den Container löschen

Was mich am meisten verdutzt an der Aussage des Freundes war, das das Löschen eines Containers so ein großer Akt ist. Auf Arbeit mache ich das ständig. Das liegt primär daran, das wir mit docker-compose arbeiten und das selbst für jeden kleinsten Dienst. Das arbeiten mit compose hat den großen Vorteil, das wir die Konfiguration der Docker Container in einer YAML-Konfigurationsdatei gespeichert ist und diese Datei über GIT versioniert ist.

Einen Dienst mit docker-compose einrichten

Privat benutze ich für ein Projekt odoo und verwende docker-compose für die Konfiguration der Anwendung. Hier meine komplette docker-compose.yml:

 1 2 3 4 5 6 7 8 9101112131415161718192021222324
version: '2'
services:
  web:
    image: odoo:10.0
    depends_on:
      - db
    volumes:
      - ./data:/var/lib/odoo
      - ./config:/etc/odoo
      - ./addons:/mnt/extra-addons
    ports:
      - "127.0.0.1:18069:8069"
    environment:
      - HOST=db
      - USER=odoo
      - PASSWORD=somethingrandom
  db:
    image: postgres:9.6
    volumes:
        - ./postgres:/usr/local/var/lib/postgresql
    environment:
        - "PGDATA=/usr/local/var/lib/postgresql"
        - "POSTGRES_PASSWORD=somethingrandom"
        - "POSTGRES_USER=odoo"

Ich habe hier 2 Container definiert. Einmal den Container web für die Anwendung an sich, in diesem Fall habe ich mit image: odoo:10.0 ein Image aus dem Hub angegeben, man könnte aber auch ein Dockerfile angeben, dann würde docker-compose zuerst das image bauen. Als nächstes habe ich mit depends_on definiert, das der Container web von dem Container db abhängig ist, das bedeuted auch, das zuerst db gestartet wird und dann web. Ob die PostgreSQL-Datenbank so schnell hoch fährt, das sie berreit ist, wenn die Anwendung sie benötigt ist unter Umständen nicht gegeben, kann also je nach Anwendungen zu Problemen führen und sollte man im Hinterkopf behalten. Im Fall von Odoo ist das allerdings kein Problem. Die Abhängigkeit bietet auch den Vorteil das compose einen Eintrag in der Datei /etc/hosts vor nimmt, so dass man den Datenbank-Container mit seinem Namen referenzieren kann. Mit dem Eintrag volumes spezifiziere ich 3 bind mounts die von dem lokalen Dateisystem in das Container Dateisystem zeigen. Über das Schlüsselwort ports kann ich Port-Weiterleitungen von dem Host-System in den Container machen. Dabei sollte man beachten das man Ports immer lokal bindet und nicht auf allen Geräten zur Verfügung steht. Über environment kann man zusätzliche Umgebungsvariablen in den Container reinreichen. Bei dem PostgreSQL-Image gibt es z.B. einige Variablen mit dem man das Image konfigurieren kann.

Starten tut man die Komposition mit dem Befehl up:

1
$ docker-compose up

Wenn das System ordentlich hoch fährt kann man es mit STRG+C beenden und dann mit dem parameter -d starten, damit es im Hintergrund verschwindet:

1
$ docker-compose up -d

Will man die Dienste runterfahren gibt es den Befehl down:

1
$ docker-compose down

Dabei werden sogar etwaige Container schon gelöscht. Aber da der komplette Zustand der beiden Container außerhalb im aktuellen Verzeichnis des Host-Systems gespeichert wird, ist das alles kein Problem.

Fazit

Alles in allem hoffe ich, das ich darlegen konnte, wieso Container Zustandslos (stateless) und Unveränderbar (immuteable) sein sollen. Zusätzlich sollte man sich immer bei Docker überlegen wo die Daten sind, es ist nämlich schnell und sehr leicht passiert, das die Daten im Container sind, weil man sich das Image nicht genau angesehen hat oder sich auch einfach nicht überlegt hat wo wie sein könnten.