FabAccess Konfiguration

Nach jeder Installation ist eine Anpassung (Konfiguration) an die nötigen Umstände sinnvoll und wünschenswert.

Server Logs konfigurieren

Der Log Level und die Formatierung von BFFH können über folgende Wege konfiguriert werden.

Umgebungsvariable

Über die Umgebungsvariable BFFH_LOG=debug - zum Beispiel eingebunden per systemd Service

Unterstützte Level für BFFH_LOG sind:

  • info
  • warn
  • error
  • debug
  • trace

Parameter in bffhd

Über bffhd Parameter kann das Kommando beliebig angepasst werden (und ebenso im systemd Service verwendet werden):

--log-format

Full

2024-12-07T11:10:48.198579Z DEBUG bffh:tls: difluoroborane::tls: TLS secret logging is disabled. keylog=false
2024-12-07T11:10:48.198685Z DEBUG bffh:tls: difluoroborane::tls: reading certificates path=/etc/ssl/fablabchemnitz.de.cert.pem
2024-12-07T11:10:48.198959Z DEBUG bffh:tls: difluoroborane::tls: reading private key path=/etc/ssl/fablabchemnitz.de.privkey.pem
2024-12-07T11:10:48.199608Z DEBUG difluoroborane::actors::process: Process actor updating state name=actor-process-test cmd=/opt/fabinfra/actor-process-test.sh state=SendState(ArchivedState { inner: ArchivedMachineState { state: Free, previous: Some(ArchivedUserRef { id: "local_lab_admin" }) } })
2024-12-07T11:10:48.200451Z DEBUG difluoroborane::actors::process: Process actor updating state name=Tasmota_Mjolnir cmd=/opt/fabinfra/adapters/tasmota/main.py state=SendState(ArchivedState { inner: ArchivedMachineState { state: InUse(ArchivedUserRef { id: "local_lab_admin" }), previous: Some(ArchivedUserRef { id: "local_lab_admin" }) } })
2024-12-07T11:10:48.203817Z  INFO bffh:binding API listen sockets: difluoroborane::capnp: Opened listen socket on 127.0.0.1:5961
2024-12-07T11:10:48.204079Z  INFO bffh:binding API listen sockets: difluoroborane::capnp: Opened listen socket on 192.168.1.192:5961

Compact

Das kompakte Layout ist identisch zu Full

2024-12-07T11:10:48.198579Z DEBUG bffh:tls: difluoroborane::tls: TLS secret logging is disabled. keylog=false
2024-12-07T11:10:48.198685Z DEBUG bffh:tls: difluoroborane::tls: reading certificates path=/etc/ssl/fablabchemnitz.de.cert.pem
2024-12-07T11:10:48.198959Z DEBUG bffh:tls: difluoroborane::tls: reading private key path=/etc/ssl/fablabchemnitz.de.privkey.pem
2024-12-07T11:10:48.199608Z DEBUG difluoroborane::actors::process: Process actor updating state name=actor-process-test cmd=/opt/fabinfra/actor-process-test.sh state=SendState(ArchivedState { inner: ArchivedMachineState { state: Free, previous: Some(ArchivedUserRef { id: "local_lab_admin" }) } })
2024-12-07T11:10:48.200451Z DEBUG difluoroborane::actors::process: Process actor updating state name=Tasmota_Mjolnir cmd=/opt/fabinfra/adapters/tasmota/main.py state=SendState(ArchivedState { inner: ArchivedMachineState { state: InUse(ArchivedUserRef { id: "local_lab_admin" }), previous: Some(ArchivedUserRef { id: "local_lab_admin" }) } })
2024-12-07T11:10:48.203817Z  INFO bffh:binding API listen sockets: difluoroborane::capnp: Opened listen socket on 127.0.0.1:5961
2024-12-07T11:10:48.204079Z  INFO bffh:binding API listen sockets: difluoroborane::capnp: Opened listen socket on 192.168.1.192:5961

Pretty

  2024-12-07T11:09:18.093419Z DEBUG difluoroborane::tls: reading certificates, path: /etc/ssl/fablabchemnitz.de.cert.pem
    at bffhd/tls.rs:113
    in difluoroborane::tls::tls
    in bffh::bffh

  2024-12-07T11:09:18.093835Z DEBUG difluoroborane::tls: reading private key, path: /etc/ssl/fablabchemnitz.de.privkey.pem
    at bffhd/tls.rs:123
    in difluoroborane::tls::tls
    in bffh::bffh

  2024-12-07T11:09:18.097839Z  INFO difluoroborane::capnp: Opened listen socket on 127.0.0.1:5961
    at bffhd/capnp/mod.rs:99
    in difluoroborane::capnp::binding API listen sockets
    in bffh::bffh

  2024-12-07T11:09:18.098140Z  INFO difluoroborane::capnp: Opened listen socket on 192.168.1.192:5961
    at bffhd/capnp/mod.rs:99
    in difluoroborane::capnp::binding API listen sockets
    in bffh::bffh

--log-level

Die Level sind die gleichen wie BFFH_LOG

Siehe auch Cheat Sheet - Wichtigste Befehle (Übersicht)

--verbose (-v)

Dieser Parameter kann bis zu drei mal als Argument angegeben werden und erhöht die Log-Ausgabe zusätzlich. Beispiel:

opt/fabinfra/bffh/target/release/bffhd --config /opt/fabinfra/bffh-data/config/bffh.dhall --verbose --verbose --verbose

oder

opt/fabinfra/bffh/target/release/bffhd --config /opt/fabinfra/bffh-data/config/bffh.dhall -vvv

--tls-key-log

Dieser Parameter wird nur für Entwickler benötigt. Wenn für Debug Zwecke der Inhalt der verschlüsselten Verbindungen eingesehen werden soll, werden in der angegeben Datei <PATH> die Schlüssel für jede Verbindung gespeichert und können z.B. von Wireshark geladen werden.

Log File schreiben

Wer bffhd nicht über systemd startet und deshalb auch keine Logs mit journalctl auslesen möchte, der kann den Output auch konventiell in eine Log-Datei schreiben - hier im Beispiel bffh.log. Wir fangen dabei die Output-Streams stdout und stderr gemeinsam in einem Ausgabestrom ab (2>&1). Den Parameter --log-level verwenden wir nicht, da er bei der normalen Systemausgabe ignoriert wird (siehe Issue #83) und führen stattdessen mit BFFH_LOG an:

BFFH_LOG=debug /opt/fabinfra/bffh/target/release/bffhd --config /opt/fabinfra/bffh-data/config/bffh.dhall --log-format Pretty > bffh.log 2>&1

Audit Log

Der Audit Log ist das Log File, was bffhd schreibt und in bffh.dhall konfiguriert wird. Es gibt Aufschluss über "Wer hat wann welche Ressource genutzt oder zurückgegeben?". Details finden sich in Audit Log (Revisionsprotokoll)

Benutzerkonfiguration - user.toml

In der TOML-Datei users.toml werden die Benutzer, ihr Passwort, Rollen und Kartenschlüssel gespeichert. Die Datei befindet sich üblicherweise in /etc/bffh/. Die Datei wird nicht automatisch von bffh geladen - egal, ob sie komplett neu ist oder nur modifiziert wurde. Das Importieren der users.toml erfolgt durch bffhd --load. Details siehe Cheat Sheet - Wichtigste Befehle (Übersicht).

Beispielrollen

Rollen werden in der bffh.dhall-Konfiguration definiert. Das Docker-compose Repository https://gitlab.com/fabinfra/fabaccess/dockercompose hat ein gutes Beispiel:

[Admin1]
roles = ["Admin"]
passwd = "secret"
cardkey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

[Admin2]
roles = ["Admin"]
passwd = "secret"
cardkey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

[ManagerA1]
roles = ["ManageA", "UseA", "ReadA", "DiscloseA"]
passwd = "secret"
cardkey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

[ManagerA2]
roles = ["ManageA", "UseA", "ReadA", "DiscloseA"]
passwd = "secret"
cardkey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

[ManagerB1]
roles = ["ManageB", "UseB", "ReadB", "DiscloseB"]
passwd = "secret"
cardkey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

[ManagerB2]
roles = ["ManageB", "UseB", "ReadB", "DiscloseB"]
passwd = "secret"
cardkey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

[ManagerC1]
roles = ["ManageC", "UseC", "ReadC", "DiscloseC"]
passwd = "secret"
cardkey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

Manuelles Anlegen neuer Benutzer

Lasse alle neuen Nutzer ihre Passwörter verschlüsselt an den Admin übertragen, indem diese ihr Passwort als verschlüsselten Argon2 String übermitteln.

argon2 Passwort Auf der Kommandozeile erzeugen ...

... mit Ubuntu Linux

apt install argon2
PASSWORD="mypassword" && echo | argon2 $PASSWORD -i -k 4096 -p 1 -t 3 -l

... mit Windows

Eine Implementierung für Windows gibt es zum Beispiel unter https://github.com/philr/argon2-windows

Wir laden das Release-Zip herunter, entpacken es und führen Argo2Opt.exe aus:

set PASSWORD="mypassword"
echo %PASSWORD% | Argon2Opt.exe %PASSWORD% -i -k 4096 -p 1 -t 3 -l 16

Wir übermitteln die kodierte Zeichenkette in die users.toml, hier im Beispiel: $argon2i$v=19$m=4096,t=3,p=1$UEFTU1dPUkQ$VMwncjCWdW+f6x8qzshLaA. Der Eintrag in der users.toml könnte so lauten:

[vmario]
roles = ["mitglied"]
passwd = "$argon2i$v=19$m=4096,t=3,p=1$UEFTU1dPUkQ$VMwncjCWdW+f6x8qzshLaA"
cardkey = "70AFE9E6B1D6352313C2D336ADC2777A"

argon2 Passwort mit Argon2 Hash Generator Tool

Settings:

argon2.png

Wir übermitteln die kodierte Zeichenkette, hier im Beispiel: $argon2i$v=19$m=4096,t=3,p=1$QkkyWERYNEdnQVBWSzhTQg$Lv2mrx+YRtoNrV1eDjhZcg

Hauptkonfiguration - bffh.dhall

BFFH verwendet Dhall für die Struktur der Konfigurationsdateien BFFH verwendet RBAC für die Zugriffskontrolle.

Die Konfiguration von BFFH befindet sich in der Datei bffh.dhall. Die Datei kann auch umbenannt werden. Wichtig ist, dass sie dann überall korrekt referenziert wird (z.B. in Service Scripts).

Tipps & Tricks in Dhall

Kommentare

Zeilen, die mit -- beginnen, werden automatisch als Kommentar gewertet

Imports, Umgebungsvariablen, URLs - Dhall-Konfigurationen verschlanken

Innerhalb einer Dhall können weitere Dhall-Dateien referenziert werden (Import Statements). Dadurch lassen sich Konfigurationen schlank und visuell gut in kleine Blöcke aufteilen. Außerdem lassen sich auch Umgebungsvariablen verwenden, sowie Inhalte aus URLs abrufen. Siehe https://github.com/dhall-lang/dhall-lang/blob/master/standard/imports.md

    -- Aus einer URL importierter Ausdruck
    let concatSep = http://prelude.dhall-lang.org/Prelude/Text/concatSep sha256:fa909c0b2fd4f9edb46df7ff72ae105ad0bd0ae00baa7fe53b0e43863f9bd34a

in  { name = env:USER as Text -- Aus der Umgebung importierte Ausdrücke
    , age  = 23
    , publicKey = ~/.ssh/id_rsa.pub as Location  -- Pfad gelesen als Dhall-Ausdruck
    , hobbies = concatSep ", " [ "piano", "reading", "skiing" ]
    } : ./schema.dhall  -- Aus einer Datei importierter Ausdruck

Sie können Importe mit Integritätsprüfungen schützen, wenn Sie einen SHA-256-Hash anhängen (wie beim obigen concatSep-Import), und Sie können einen Wert auch als Rohtext importieren, indem Sie ihn as Text anhängen (wie beim obigen env:USER-Import), oder als aufgelösten Pfad, indem Sie ihn as Location anhängen.

Importierte Ausdrücke können transitiv andere Ausdrücke importieren. Die oben importierte Datei ./schema.dhall kann zum Beispiel auch andere Dateien importieren:

-- ./schema.dhall

{ name : ./schema/name.dhall
, age  : ./schema/age.dhall
, hobbies : ./schema/hobbies.dhall
}

... und wenn ./schema/hobbies.dhall enthielte einen relativen Import wie z.B.:

-- ./schema/hobbies.dhall

List ./hobby.dhall

... dann würde sich der relative Import von ./hobby.dhall tatsächlich auf ./schema/hobby.dhall beziehen. Dies ist als "Importverkettung" bekannt: die Auflösung von Importen relativ zur Position des aktuellen Ausdrucks.

Die Konfiguration von bffh

Allgemeine Einstellungen

listens

Enthält die Adressen, auf die BFFH bei der Verbindung für die API hört. Standardport für BFFH ist 59661

Beispiel:

listens = 
[ 
    { address = "127.0.0.1", port = Some 59661 }
]

mqtt_url

Enthält die Adresse des MQTT-Servers, mit dem sich BFFH verbindet.

Die Adresse hat das Format <protocol>://[user]:[password]@<server>:[port]

  • protocol wird benötigt und kann eins der folgenden Werte annehmen: mqtt,tcp,mqtts,ssl
  • user und password sind optional
  • server ist erforderlich und kann eine IP-Adresse oder ein Hostname sein
  • port ist optional. Der Standardport ist 59661

Beispiele:

mqtt_url = "tcp://localhost:1883" 
mqtt_url = "mqtts://user:password@server.tld:port"

db_path

Enthält den Pfad für die interne Datenbank, die BFFH verwendet. BFFH wird zwei Dateien erstellen: <db_path> und <db_path>-lock. Es sollte sichergestellt werden, dass BFFH Schreibzugriff auf das entsprechende Verzeichnis hat.

Beispiel:

db_path = "/tmp/bffh"

Berechtigungen (Permissions)

Standardberechtigungen

BFFH verfügt über einige Standardberechtigungen, die der Verwaltung und den Admin-Rechten zugewiesen werden können.

bffh.users.info - Nutzerliste bekommen und Infos über diese Accounts erhalten
bffh.users.manage - Nutzerliste bekommen und Nutzer verwalten
bffh.users.admin - Globale Administration: Nutzer hinzufügen, löschen, ändern (z.B. Passwort-Reset)

Modellieren von Berechtigungen

Allgemeines Schema: space.type.category.permission.model

Administrator
space.machines.printers.*

Offene Berechtigung
space.machines.printers.read.*

BFFH verwendet eine pfadähnliche Zeichenkette als Erlaubnisformat, getrennt durch einen . Punkt. So besteht zum Beispiel this.is.a.permission aus den Teilen this, is, a und permission. Bei der Anforderung von Berechtigungen, z. B. in Maschinen, muss immer eine genaue Berechtigung angegeben werden, also z. B. test.write. Bei der Erteilung von Berechtigungen, z. B. in Rollen, können entweder eine genaue Berechtigung angegeben oder die beiden Platzhalter * und + verwendet werden. Diese Wildcards verhalten sich ähnlich wie Regex- oder Bash-Wildcards:

  • * gewährt alle Berechtigungen in diesem Teilbaum. So wird perms.read.* für jedes von passen: 
    • perms.read
    • perms.read.machineA
    • perms.read.machineB
    • perms.read.machineC.manage
  • + gewährt alle Berechtigungen unter des Wertes. So wird perms.read.+* für jedes von passen: 
    • perms.read.machineA
    • perms.read.machineB
    • perms.read.machineC.manage
    • aber nicht perms.read

Wildcards sind wahrscheinlich am nützlichsten, um Maschinen zu gruppieren, z.B. 3D-Drucker und eine Bandsäge:

  1. Write (schreiben) Berechtigungen
    • machines.printers.write.prusa.sl1
    • machines.printers.write.prusa.i3
    • machines.printers.write.anycubic
    • machines.bandsaws.write.bandsaw1
  2. Manage (verwalten) Berechtigungen
    • machines.printers.manage.prusa.sl1
    • machines.printers.manage.prusa.i3
    • machines.printers.manage.anycubic
    • machines.bandsaws.manage.bandsaw1
  3. Admin Berechtigungen
    • machines.printers
      • Für alle Drucker
    • machines.bandsaws
      • Für alle Bandsägen

Dann erteilen wir den Rollen die entsprechenden Rechte:

  • Nutze beliebige 3D-Drucker:
    • machines.printers.write.+
  • Erlaube nur die Nutzung "billiger" Drucker:
    • machines.printers.write.anycubic.*
    • machines.printers.write.prusa.i3
  • Erlaube das Verwalten der Drucker:
    • machines.printers.+
  • Erlaubte das Administrieren aller Drucker:
    • machines.printers.*

Auf diese Weise klappt es trotzdem mit der Aufteilung, wenn später ein weitere Anycubic Drucker gekauft wird:

  • machines.printers.write.anycubic.i3
  • machines.printers.write.anycubic.megax

Konfiguration von Maschinen

machines

Enthält eine Liste der definierten Maschinen. Die Maschinen haben verschiedene Wahrnehmungsebenen, mit denen interagiert werden kann:

  • disclose (offenlegen): Benutzer kann die Maschine in der Maschinenliste sehen
  • read (lesen): Der Benutzer kann Informationen über die Maschine und ihren Zustand lesen
  • write (schreiben): Der Benutzer kann die Maschine benutzen
  • manage (verwalten): Der Benutzer kann als Manager mit dem System interagieren (Prüfen, Freigeben, Transferieren)

Jede Maschine muss eine ID haben, um in anderen Teilen dieser Konfiguration oder über die API auf die Maschine verweisen zu können. Und jede Maschine muss einen Namen haben.

Optionale Informationen

Um weitere Informationen über die Maschine bereitzustellen, können diesen Beschreibungen hinzugefügt oder ein externer Wiki-Link bereitstellt werden. Beide Attribute sind nur optional und müssen nicht gesetzt werden.

Beispiel:

machines = 
{ 
    machine123 = 
    { 
        name = "Testmachine",
        description = Some "A test machine",
        wiki = "https://someurl"

        disclose = "lab.test.read",
        read = "lab.test.read",
        write = "lab.test.write",
        manage = "lab.test.admin"
    }
}

“machine123” is in this case the “Machine-ID”

Konfiguration von Rollen (roles)

Die Rollen werden in der Datei bffh.dhall konfiguriert. Wenn die Datei roles.toml im Verzeichnis vorhanden ist, kann sie gelöscht werden und kann nicht zur Verwaltung von Rollen verwendet werden.

roles

Enthält die Liste der definierten Rollen. Rollen haben eine Liste von Berechtigungen und können vererbt werden. Die Berechtigung kann ein Platzhalter in der Berechtigungsliste sein.

Beispiel:

roles =
{
    testrole = 
    {
        permissions = [ "lab.test.*" ]
    },
    somerole =
    { 
        parents = [ "testparent" ],
        permissions = [ "lab.some.admin" ]
    }, 
    testparent =
    {
        permissions =
        [
            "lab.some.write",
            "lab.some.read",
            "lab.some.disclose"
        ]
    }
}

Konfiguration von Aktoren (actors)

actors

Enthält eine Liste von Aktoren. Aktoren werden durch ein Modul und einen oder mehrere Parameter definiert. Aktuell von Haus aus unterstützte Aktoren (ohne zusätzliche Plugins) sind:

Dummy Actor

Für Testzwecke kann ein interner Dummy-Initiator genutzt werden:

Der „Dummy“-Initiator versucht alle paar Sekunden, einen Rechner als den angegebenen Benutzer zu verwenden und zurückzugeben. Er ist gut geeignet, um das System zu testen, führt aber zu Spam im Log und ist daher standardmäßig deaktiviert.   

actors = { 
   Dummy_123 = 
      { 
          module = "Dummy", 
          params = {=}
      } 
   }

Shelly Actor

Dieser Aktor verbindet BFFH über einen MQTT-Server mit einem Shelly Gerät.

Der topic Parameter des Shelly muss auf das Shelly-spezifische MQTT-Topic gesetzt werden.

Anleitung zum Auffinden des Shelly Topic

Beispiel:

actors = 
{
    Shelly_123 = 
    { 
        module = "Shelly", 
        params = 
        {
            topic = "shellyplug-s-123456"
        }
    }
}

„Shelly_123“ ist in diesem Fall die "Actor-ID".

Process Actor

Dieser Aktor ermöglicht es, eigene Prozesse (z.B. Python, Bash, Perl, Java ...) mit dem BFFH Server zu verbinden. Im Beispiel heißt unser Aktor Bash_123.

cmd = Pfad der ausführbaren Datei
args = Argumente der ausführbaren Datei

Beispiel:

actors = 
{
    Bash_123 =
    { 
        module = "Process", 
        params =
            { 
                cmd = "./examples/actor.sh",
                args = "your ad could be here"
            }
    }
}

Konkret nutzbare Aktoren-Beispiele finden sich hier.

actor_connections

Verbindet den Aktor mit einer Maschine. Eine Maschine kann mehrere Aktoren haben. Verwenden Sie die "Machine-ID" (machine) und "Actor-ID" (actor).

Beispiel:

actor_connections = 
[
    { machine = "Testmachine", actor = "Shelly_123" },
    { machine = "Another", actor = "Bash_123" },
    { machine = "Yetmore", actor = "Bash_234" }
]

Konfiguration von Initiatoren (initiators)

Initiatoren werden fast genauso konfiguriert wie Aktoren.

initiators

Enthält eine Liste von Initiatoren. Initiatoren werden durch ein Modul und einen oder mehrere Parameter definiert. Die Liste kann bzw. muss leer sein, wenn keine Initiatoren verwendet werden:

initiators = {=}

Sonst kann die Liste einen oder mehrere Initiator mit ihrem Name (Im Beispiel Initiator_123) enthalten:

Dummy Initiator

Für Testzwecke kann ein interner Dummy-Initiator genutzt werden:

Der „Dummy“-Initiator versucht alle paar Sekunden, einen Rechner als den angegebenen Benutzer zu verwenden und zurückzugeben. Er ist gut geeignet, um das System zu testen, führt aber zu Spam im Log und ist daher standardmäßig deaktiviert.   

initiators = { 
   Initiator_123 = 
      { 
          module = "Dummy", 
          params = 
          { 
              uid = "Testuser" 
          } 
      } 
   }
Process Initiator

Dieser Initiator ermöglicht es, eigene Prozesse (z.B. Python, Bash, Perl, Java ...) mit dem BFFH Server zu verbinden. Im Beispiel heißt unser Initiator Bash_567.

cmd = Pfad der ausführbaren Datei
args = Argumente der ausführbaren Datei

Beispiel:

initiators = 
{
    Bash_567 =
    { 
        module = "Process", 
        params =
            { 
                cmd = "./examples/init.py",
                args = "your ad could be here"
            }
    }
}

Konkret nutzbare Initiatoren-Beispiele finden sich hier.

init_connections

Verknüpfung von Maschinen mit Initiatoren. Ähnlich wie bei Aktoren können einer Maschine mehrere Initiatoren zugewiesen werden, aber ein Initiator kann nur einer Maschine zugewiesen werden. Verwenden Sie für die Zuweisung die "Machine-ID" (machine) und "Initiator-ID" (initiator).

init_connections = [
    { machine = "Testmachine", initiator = "Initiator_123" }
],

FabFire

spacename

Der Name des Spaces (die offene Werkstatt, das FabLab, der HackerSpace, etc.) wird im URN-Schema urn:fabaccess:lab:{spacename} verwendet. Wird er nicht definiert, wird der Wert "generic" vergeben. Diese Angaben benötigen wir für QR-Codes von Maschinen oder für DESFire Karten zur Nutzung von FabFire.

instanceurl

Wird für eine allgemeine Space Info genutzt und als URN im Code genutzt: urn:fabaccess:lab:{spacename}\x00{instanceurl}. Dieser Wert wird aktuell nicht verwendet, muss jedoch ausgefüllt werden, damit die Konfiguration bffh.dhall valide ist!

Konfiguration per Config Generator

Siehe Einfache Konfiguration mit dem FabAccess Config Generator

Konfiguration exportieren

BASE="/opt/fabinfra/bffh/target/release"
CFG="$DATA/config/bffh.dhall"
$BASE/bffhd --verbose --config $CFG --dump-users /tmp/users.toml --force

Siehe auch Cheat Sheet - Wichtigste Befehle (Übersicht)

Wie nutze ich die Föderation?

Stand der Föderation

Das Feature Föderation ist aktuell (Stand Dezember 2024) noch nicht in FabAccess implementiert.

Was muss ich beachten, wenn wir mit Werkstätten föderieren wollen?

Das allgemeine Konzept der Föderation ist hier erklärt.

Wo finde ich Föderationspartner? Wo können wir uns eintragen?

Föderationspartner für FabAccess findest du unter anderem auf der Seite des Verbunds Offener Werkstätten oder auch hier auf unserer Webseite. Siehe hier.

user.toml aus verschiedenen Nutzerdatenbanken erzeugen

LDAP Anbindung

Hierzu gibt es einen dedizierten Artikel.

Authentik

Nutzer geben ein zweites Passwort an und ein Skript checkt jede Minute Authentik ob es neue Nutzer gibt, sich Gruppen/Passwort geändert haben und nimmt entsprechend Änderungen über die FabAccess API vor. Das Passwort wird dann aus den Nutzerattributen gelöscht. Die Daten liegen in einer MySQL Datenbank.

Michael Prange vom MakerSpace Gütersloh fragen!

CiviCRM

CiviCRM erlaubt den Export von Nutzern als CSV-Tabelle. Diese kann verwendet werden, um Nutzer in FabAccess zu importieren.

Roy Böttcher vom MakerSpace Leipzig fragen! 

VereinOnline

Nutzer und Gruppen aus VereinOnline lassen sich mit dem Tool csv-to-fabaccess-user-toml vom Sternenlabor Plauen direkt als users.toml ausgeben. Dazu wird eine Datei namens Mitglieder.csv mit Spaltenseparator ; und den Spalten Login und Gruppen (mit Separator ,) erzeugt. Für das Tool muss Node.js installiert sein.

cd ~
git clone https://github.com/Sternenlabor/csv-to-fabaccess-user-toml.git
cd csv-to-fabaccess-user-toml/

sudo apt install nodejs

npm install csv-parser

Mitglieder-CSV Datei - Beispiel:

https://docs.fab-access.org/data-csv-to-fabaccess-user-toml/Mitglieder.csv

# Ausführen
node run.mjs

[
  { Login: 'Anton', Gruppen: 'Admins' },
  { Login: 'Peter', Gruppen: 'Mitglieder,Admins' },
  { Login: 'Jonny', Gruppen: 'Mitglieder' },
  { Login: 'Sarah', Gruppen: 'Mitglieder' }
]
users: 4
File created successfully

Beispiel-Output:

cat user.toml
[Admin1]
roles = [ "Admin" ]
passwd = "secret"

[Anton]
roles = [ "User", "Admins" ]
passwd = "secret"

[Peter]
roles = [ "User", "Mitglieder,Admins" ]
passwd = "secret"

[Jonny]
roles = [ "User", "Mitglieder" ]
passwd = "secret"

[Sarah]
roles = [ "User", "Mitglieder" ]
passwd = "secret"

easyVerein

Für das Programm easyVerein sind noch keine Schnittstellen in der Community bekannt. Jedoch nutzen es viele offene Werkstätten.

Cheat Sheet - Wichtigste Befehle (Übersicht)

bffh Daemon (bffhd) Befehlsübersicht (-h, --help)

/opt/fabinfra/bffh/target/release/bffhd --help
-c, --config <config>            Path to the config file to use
    --check                      Check config for validity
    --dump                       Dump all internal databases
    --dump-users <FILE>          Dump the users db to the given file as TOML
    --force                      force ops that may clobber
-h, --help                       Print help information
    --load <load>                Load values into the internal databases
    --log-format <log format>    Use an alternative log formatter. Available: Full, Compact,
                                 Pretty [possible values: Full, Compact, Pretty]
    --log-level <log level>      Set the desired log levels.
    --print-default              Print a default config to stdout instead of running
    --quiet                      Decrease logging verbosity
    --tls-key-log [<PATH>...]    log TLS keys into PATH. If no path is specified the value of
                                 the envvar SSLKEYLOGFILE is used.
-v, --verbose                    Increase logging verbosity
-V, --version                    Print version information

Logging-Konfiguration (--log-level, --log-format, --quiet, -v, --verbose, --tls-key-log)

Log-spezifische Parameter inklusive Audit Log sind zusammenfassend in Server Logs konfigurieren genauer erklärt.

Überschreibende Operationen erzwingen (--force)

Eklärung ToDo

Benutzerdatenbank importieren (--load <users.toml file>)

Dieses Kommando lädt die angegebene *.toml Datei in die interne Benutzerdatenbank (state db file namens bffh) hinein. Praxistipps zum Umgang mit der users.toml findest du unter Nutzerdatenbank laden / hashen / prüfen.

BFFH Datenbank exportieren (--dump)

Die BFFH Datenbank soll sich ebenso dumpen lassen. Dazu muss --config <Pfad zu bffh.dhall> angegeben werden, damit bffhd weiß, welche Datenbank angefragt werden soll.

/opt/fabinfra/bffh/target/release/bffhd -c /opt/fabinfra/bffh-data/config/bffh.dhall --dump
Error: 
  × DB Dumping is currently not implemented, except for the users db, using `--dump-users`

Dieser Befehl ist aktuell nicht implementiert!

Benutzerdatenbank exportieren (--dump-users)

Einmal importiere Nutzerdaten können genauso wieder aus der bffh Datenbank exportiert werden. Dazu muss --config <Pfad zu bffh.dhall> angegeben werden, damit bffhd weiß, welche Datenbank angefragt werden soll.

/opt/fabinfra/bffh/target/release/bffhd -c /opt/fabinfra/bffh-data/config/bffh.dhall --dump-users

Konfigurationsdatei prüfen (--check)

Dieser Parameter prüft die angegebene Konfigurationsdatei auf fehlende oder fehlerhafte Angaben und überprüft generell, ob die *.dhall Datei geparsed werden kann. Sobald eine z.B. eine eckige oder geschweifte Klammer, ein Hochkomma oder normales Komma fehlt, gibt es in der Regel Probleme. Für das Prüfen muss ebenso --config <Pfad zu bffh.dhall> angegeben werden

Konfigurationsstandard ausgeben (--print-default)

/opt/fabinfra/bffh/target/release/bffhd --print-default

Dieser Befehl gibt eine minimale Beispielkonfiguration im Dhall-Format aus. Die ausführliche Erläuterung der bffh-Konfiguration findest du hier.

{
actor_connections = [
  {
    _1 = "Testmachine",
    _2 = "Actor"
    }
  ],
actors = {
  Actor = {
    module = "Shelly",
    params = {=} 
  }
  },
auditlog_path = "/var/log/bffh/audit.log", 
certfile = "./bffh.crt", 
db_path = "/run/bffh/database", 
init_connections = [
  { 
    _1 = "Initiator", 
    _2 = "Testmachine"
    }
  ],
initiators = {
  Initiator = {
    module = "TCP-Listen",
    params = {=}
    }
  },
instanceurl = "",
keyfile = "./bffh.key",
listens = [
  {
    address = "127.0.0.1"
  }
],
machines = {=},
mqtt_url = "tcp://localhost:1883",
roles = {=},
spacename = ""
}

Version anzeigen (--version, -V)

/opt/fabinfra/bffh/target/release/bffhd -V
diflouroborane 0.4.2

Helfer-Skripte

Diverse Helfer-Scripts, die verschiedene Optionen/Parameter automatisieren (z.B. Benutzerdankbank sichern) finden sich in der Script-Sammlung.

Einfache Konfiguration mit dem FabAccess Config Generator

Wolfram vom MakerSpace Leipzig hat 2024 ein umfangreiches Tool - den FabAccess Config Generator - entwickelt, um eine Konfiguration (bffh.dhall) anhand einer Maschinenliste im CSV-Format zu erzeugen.

Funktionsumfang

Funktionsweise

Die CSV-Datei enthält pro Zeile einen Eintrag für eine Maschine. Neben Angaben zur Maschine selbst (ID, Wiki-URL etc.) werden Angaben zum Bereich vermerkt, in dem sich die Maschine befindet. Aus den Angaben zu den Bereichen werden dann alle Rollen abgeleitet.

Vollständige Dokumentation

Hier ist nur eine kurze Erklärung über das HowTo abgebildet. Eine umfangreiche und aktuelle Dokumentation findest du hier: https://elem74.github.io/fabaccess-config-generator-docs

Installation

Das Tool kann direkt auf dem Server oder auf einem beliebigen Client installiert werden. Das ist Geschmackssache.

cd /opt/fabinfra/
git clone https://github.com/elem74/fabaccess-config-generator.git
cd fabaccess-config-generator/

Anpassungen

Alle Erklärungen zum Konzept und zu verschiedenen Einstellmöglichkeiten finden sich im Repository unter https://github.com/elem74/fabaccess-config-generator/tree/main?tab=readme-ov-file#nutzung-des-python-skripts

Config Generator verwenden

Konfigurationsdatei maschinenliste.csv mit dem favorisierten Editor bearbeiten:

vim maschinenliste.csv

http://docs.fab-access.org/data-fabaccess-config-generator/maschinenliste.csv

Danach kann der Generator ausgeführt werden:

python3 config-generator.py

Das Tool schreibt automatisch ins Unterverzeichnis output/ die folgenden Dateien:

bffh-dhall-data.txt

Die Beispiel Dhall Daten sehen dabei wie folgt aus:

bffh-dhall-data.txt
	roles = {
		Admin = {
			permissions =  [
				"bffh.users.manage",
				"bffh.users.info",
				"bffh.users.admin",
				"beispielw.*",
			]
		},
 
		_manager_schichtleitung = {
			permissions =  [
				"beispielw.*",
			]
		},
 
		beispielw_holz_manager = {
			permissions =  [
				"beispielw.holz.*",
			]
		},
 
		beispielw_holz_bandsaege_user = {
			permissions =  [
				"beispielw.holz.bandsaege.disclose.*",
				"beispielw.holz.bandsaege.read.*",
				"beispielw.holz.bandsaege.write.*",
			]
		},
 
		beispielw_holz_hobelmaschine_user = {
			permissions =  [
				"beispielw.holz.hobelmaschine.disclose.*",
				"beispielw.holz.hobelmaschine.read.*",
				"beispielw.holz.hobelmaschine.write.*",
			]
		},
 
		beispielw_holz_holzcnc_user = {
			permissions =  [
				"beispielw.holz.holzcnc.disclose.*",
				"beispielw.holz.holzcnc.read.*",
				"beispielw.holz.holzcnc.write.*",
			]
		},
 
		beispielw_holz_user = {
			permissions =  [
				"beispielw.holz.disclose.*",
				"beispielw.holz.read.*",
				"beispielw.holz.write.*",
			]
		},
 
		beispielw_textil_manager = {
			permissions =  [
				"beispielw.textil.*",
			]
		},
 
		beispielw_textil_user = {
			permissions =  [
				"beispielw.textil.disclose.*",
				"beispielw.textil.read.*",
				"beispielw.textil.write.*",
			]
		},
 
		beispielw_fablab_manager = {
			permissions =  [
				"beispielw.fablab.*",
			]
		},
 
		beispielw_fablab_3dprint_manager = {
			permissions =  [
				"beispielw.fablab.3dprint.*",
			]
		},
 
		beispielw_fablab_3dprint_user = {
			permissions =  [
				"beispielw.fablab.3dprint.disclose.*",
				"beispielw.fablab.3dprint.read.*",
				"beispielw.fablab.3dprint.write.*",
			]
		},
 
		beispielw_fablab_laser_manager = {
			permissions =  [
				"beispielw.fablab.laser.*",
			]
		},
 
		beispielw_fablab_laser_user = {
			permissions =  [
				"beispielw.fablab.laser.disclose.*",
				"beispielw.fablab.laser.read.*",
				"beispielw.fablab.laser.write.*",
			]
		},
 
		beispielw_siebdruck_manager = {
			permissions =  [
				"beispielw.siebdruck.*",
			]
		},
 
		beispielw_siebdruck_user = {
			permissions =  [
				"beispielw.siebdruck.disclose.*",
				"beispielw.siebdruck.read.*",
				"beispielw.siebdruck.write.*",
			]
		},
 
		beispielw_drucker = {
			permissions =  [
				"beispielw.siebdruck.disclose.a3drucker",
				"beispielw.siebdruck.read.a3drucker",
				"beispielw.siebdruck.write.a3drucker",
				"beispielw.buero.disclose.drucker",
				"beispielw.buero.read.drucker",
				"beispielw.buero.write.drucker",
			]
		},
 
		beispielw_buero_manager = {
			permissions =  [
				"beispielw.buero.*",
			]
		},
 
	},
	machines = {
		beispielw-holz-bandsaege-bandsaege = {
			name = "Bandsäge",
			description = "Bandsäge im Holzbereich",
			wiki = "",
			category = "Holzwerkstatt",
			disclose = "beispielw.holz.bandsaege.disclose.bandsaege",
			read = "beispielw.holz.bandsaege.read.bandsaege",
			write = "beispielw.holz.bandsaege.write.bandsaege",
			manage = "beispielw.holz.bandsaege.manage.bandsaege",
		},
 
		beispielw-holz-hobelmaschine-hobelmaschine = {
			name = "Hobelmaschine",
			description = "Hobelmaschine im Holzbereich",
			wiki = "",
			category = "Holzwerkstatt",
			disclose = "beispielw.holz.hobelmaschine.disclose.hobelmaschine",
			read = "beispielw.holz.hobelmaschine.read.hobelmaschine",
			write = "beispielw.holz.hobelmaschine.write.hobelmaschine",
			manage = "beispielw.holz.hobelmaschine.manage.hobelmaschine",
		},
 
		beispielw-holz-holzcnc-holzcncfraese = {
			name = "CNC-Fräse",
			description = "CNC Fräse im Holzbereich",
			wiki = "",
			category = "Holzwerkstatt",
			disclose = "beispielw.holz.holzcnc.disclose.holzcncfraese",
			read = "beispielw.holz.holzcnc.read.holzcncfraese",
			write = "beispielw.holz.holzcnc.write.holzcncfraese",
			manage = "beispielw.holz.holzcnc.manage.holzcncfraese",
		},
 
		beispielw-holz-kappsaege = {
			name = "Kappsäge",
			description = "Kappsäge mit allgemeiner Einweisung Holz",
			wiki = "",
			category = "Holzwerkstatt",
			disclose = "beispielw.holz.disclose.kappsaege",
			read = "beispielw.holz.read.kappsaege",
			write = "beispielw.holz.write.kappsaege",
			manage = "beispielw.holz.manage.kappsaege",
		},
 
		beispielw-holz-bandschleifer = {
			name = "Bandschleifer",
			description = "Bandschleifer mit allgemeiner Einweisung Holz",
			wiki = "",
			category = "Holzwerkstatt",
			disclose = "beispielw.holz.disclose.bandschleifer",
			read = "beispielw.holz.read.bandschleifer",
			write = "beispielw.holz.write.bandschleifer",
			manage = "beispielw.holz.manage.bandschleifer",
		},
 
		beispielw-holz-staenderbohrmaschine = {
			name = "Ständerbohrmaschine",
			description = "Ständerbohrmaschine mit allgemeiner Einweisung Holz",
			wiki = "",
			category = "Holzwerkstatt",
			disclose = "beispielw.holz.disclose.staenderbohrmaschine",
			read = "beispielw.holz.read.staenderbohrmaschine",
			write = "beispielw.holz.write.staenderbohrmaschine",
			manage = "beispielw.holz.manage.staenderbohrmaschine",
		},
 
		beispielw-textil-naehmaschine1 = {
			name = "Nähmaschine 1",
			description = "Nähmaschine Nummer 1",
			wiki = "",
			category = "Textilwerkstatt",
			disclose = "beispielw.textil.disclose.naehmaschine1",
			read = "beispielw.textil.read.naehmaschine1",
			write = "beispielw.textil.write.naehmaschine1",
			manage = "beispielw.textil.manage.naehmaschine1",
		},
 
		beispielw-textil-naehmaschine2 = {
			name = "Nähmaschine 2",
			description = "Nähmaschine Nummer 2",
			wiki = "",
			category = "Textilwerkstatt",
			disclose = "beispielw.textil.disclose.naehmaschine2",
			read = "beispielw.textil.read.naehmaschine2",
			write = "beispielw.textil.write.naehmaschine2",
			manage = "beispielw.textil.manage.naehmaschine2",
		},
 
		beispielw-fablab-3dprint-3ddrucker1 = {
			name = "3D-Drucker 1",
			description = "3D-Drucker Modell 111",
			wiki = "",
			category = "FabLab",
			disclose = "beispielw.fablab.3dprint.disclose.3ddrucker1",
			read = "beispielw.fablab.3dprint.read.3ddrucker1",
			write = "beispielw.fablab.3dprint.write.3ddrucker1",
			manage = "beispielw.fablab.3dprint.manage.3ddrucker1",
		},
 
		beispielw-fablab-3dprint-3ddrucker2 = {
			name = "3D-Drucker 2",
			description = "3D-Drucker Modell 222",
			wiki = "",
			category = "FabLab",
			disclose = "beispielw.fablab.3dprint.disclose.3ddrucker2",
			read = "beispielw.fablab.3dprint.read.3ddrucker2",
			write = "beispielw.fablab.3dprint.write.3ddrucker2",
			manage = "beispielw.fablab.3dprint.manage.3ddrucker2",
		},
 
		beispielw-fablab-laser-laser3000 = {
			name = "Lasercutter",
			description = "Modell Laser3000",
			wiki = "https://www.fiktivedoku.de",
			category = "FabLab",
			disclose = "beispielw.fablab.laser.disclose.laser3000",
			read = "beispielw.fablab.laser.read.laser3000",
			write = "beispielw.fablab.laser.write.laser3000",
			manage = "beispielw.fablab.laser.manage.laser3000",
		},
 
		beispielw-fablab-laser-kuehlung3000 = {
			name = "Kühlung",
			description = "Modell Kühlung3000",
			wiki = "",
			category = "FabLab",
			disclose = "beispielw.fablab.laser.disclose.kuehlung3000",
			read = "beispielw.fablab.laser.read.kuehlung3000",
			write = "beispielw.fablab.laser.write.kuehlung3000",
			manage = "beispielw.fablab.laser.manage.kuehlung3000",
		},
 
		beispielw-siebdruck-sdbelichter = {
			name = "SD-Belichter",
			description = "Belichter für die Siebe",
			wiki = "",
			category = "Siebdruck",
			disclose = "beispielw.siebdruck.disclose.sdbelichter",
			read = "beispielw.siebdruck.read.sdbelichter",
			write = "beispielw.siebdruck.write.sdbelichter",
			manage = "beispielw.siebdruck.manage.sdbelichter",
		},
 
		beispielw-siebdruck-a3drucker = {
			name = "A3-Drucker",
			description = "A3 Drucker im Siebdruck Bereich",
			wiki = "",
			category = "Siebdruck",
			disclose = "beispielw.siebdruck.disclose.a3drucker",
			read = "beispielw.siebdruck.read.a3drucker",
			write = "beispielw.siebdruck.write.a3drucker",
			manage = "beispielw.siebdruck.manage.a3drucker",
		},
 
		beispielw-buero-drucker = {
			name = "Drucker",
			description = "",
			wiki = "",
			category = "Büro",
			disclose = "beispielw.buero.disclose.drucker",
			read = "beispielw.buero.read.drucker",
			write = "beispielw.buero.write.drucker",
			manage = "beispielw.buero.manage.drucker",
		},
 
	},
	actors = {
        tasmota_1 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 1",
            }
        },
 
        tasmota_2 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 2",
            }
        },
 
        tasmota_6 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 6",
            }
        },
 
        tasmota_7 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 7",
            }
        },
 
        tasmota_8 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 8",
            }
        },
 
        tasmota_9 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 9",
            }
        },
 
        tasmota_10 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 10",
            }
        },
 
        tasmota_11 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 11",
            }
        },
 
        tasmota_12 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 12",
            }
        },
 
        tasmota_13 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 13",
            }
        },
 
        tasmota_14 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 14",
            }
        },
 
        tasmota_15 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 15",
            }
        },
 
        tasmota_16 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 16",
            }
        },
 
        tasmota_17 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 17",
            }
        },
 
        tasmota_18 =
        {
           module = "Process",
            params =
            {
                cmd = "/usr/local/lib/bffh/adapters/tasmota/main.py",
                args = "--host mqtt --tasmota 18",
            }
        },
 
	},    
	actor_connections = [
		{ machine = "beispielw-holz-bandsaege-bandsaege", actor = "tasmota_1" },
		{ machine = "beispielw-holz-hobelmaschine-hobelmaschine", actor = "tasmota_2" },
		{ machine = "beispielw-holz-holzcnc-holzcncfraese", actor = "tasmota_6" },
		{ machine = "beispielw-holz-kappsaege", actor = "tasmota_7" },
		{ machine = "beispielw-holz-bandschleifer", actor = "tasmota_8" },
		{ machine = "beispielw-holz-staenderbohrmaschine", actor = "tasmota_9" },
		{ machine = "beispielw-textil-naehmaschine1", actor = "tasmota_10" },
		{ machine = "beispielw-textil-naehmaschine2", actor = "tasmota_11" },
		{ machine = "beispielw-fablab-3dprint-3ddrucker1", actor = "tasmota_12" },
		{ machine = "beispielw-fablab-3dprint-3ddrucker2", actor = "tasmota_13" },
		{ machine = "beispielw-fablab-laser-laser3000", actor = "tasmota_14" },
		{ machine = "beispielw-fablab-laser-kuehlung3000", actor = "tasmota_15" },
		{ machine = "beispielw-siebdruck-sdbelichter", actor = "tasmota_16" },
		{ machine = "beispielw-siebdruck-a3drucker", actor = "tasmota_17" },
		{ machine = "beispielw-buero-drucker", actor = "tasmota_18" },
	],

mermaid-code.txt

Der Beispiel Mermaid Code sieht wie folgt aus und kann in https://mermaid.live gerendet werden.

mermaid-code.txt
%%{init: {"flowchart" : {"curve" : "linear"}}}%%
 
flowchart TD
 
 

subgraph legende["<b>Legende</b><p style="text-align:left;">👑 = Administrator
🛠️ = Manager
👷‍♂️ = Benutzer
👩‍🚀 = Benutzer (Alternativrolle)"]
end 
 

subgraph root["
    <p style="font-size: 2em">Infrastruktur</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>👑_Admin FabAccess<br>🛠️_Manager Schichtleitung</p></p>
    <p style="text-align: left; margin-top: 0px;"></p>
    <p style="opacity: 0;">.</p>
"]
end 

subgraph root_beispielw["
    <p style="font-size: 1.75em">Beispielwerkstatt </p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager Beispielwerkstatt </p></p>
    <p style="text-align: left; margin-top: 0px;"></p>
    <p style="opacity: 0;">.</p>
"]
end 
root ------ root_beispielw
 
 
root_beispielw ---- root_beispielw_buero_wrapper
subgraph root_beispielw_buero_wrapper["<p style="opacity: 0;">.</p>"]
style root_beispielw_buero_wrapper stroke: none, fill: none
 

subgraph root_beispielw_buero["
    <p style="font-size: 1.5em">Büro</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager Büro<br>👷‍♂️Benutzer Büro<br>👩‍🚀Benutzer Druckernutzung</p></p>
    <p style="text-align: left; margin-top: 0px;"><b><center>Maschinen</center></b>Drucker (👩‍🚀Benutzer Druckernutzung)</p>
    <p style="opacity: 0;">.</p>
"]
end 
 

subgraph filler_root_beispielw_buero_1["
<p style="opacity: 0;">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</p>"]
end
style filler_root_beispielw_buero_1 fill: none, stroke: none
root_beispielw_buero~~~~~~filler_root_beispielw_buero_1

subgraph filler_root_beispielw_buero_2["
<p style="opacity: 0;">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</p>"]
end
style filler_root_beispielw_buero_2 fill: none, stroke: none
filler_root_beispielw_buero_1~~~~~filler_root_beispielw_buero_2
 
 
end
 
 
root_beispielw ---- root_beispielw_fablab_wrapper
subgraph root_beispielw_fablab_wrapper["<p style="opacity: 0;">.</p>"]
style root_beispielw_fablab_wrapper stroke: none, fill: none
 

subgraph root_beispielw_fablab["
    <p style="font-size: 1.5em">FabLab</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager FabLab<br>👷‍♂️Benutzer FabLab</p></p>
    <p style="text-align: left; margin-top: 0px;"></p>
    <p style="opacity: 0;">.</p>
"]
end 

subgraph root_beispielw_fablab_3dprint["
    <p style="font-size: 1.25em">3D-Druck</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager FabLab 3D-Druck<br>👷‍♂️Benutzer FabLab 3D-Druck</p></p>
    <p style="text-align: left; margin-top: 0px;"><b><center>Maschinen</center></b>3D-Drucker 1<br>3D-Drucker 2</p>
    <p style="opacity: 0;">.</p>
"]
end 
root_beispielw_fablab ------ root_beispielw_fablab_3dprint
 

subgraph root_beispielw_fablab_laser["
    <p style="font-size: 1.25em">Laser</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager FabLab Laser<br>👷‍♂️Benutzer FabLab Laser</p></p>
    <p style="text-align: left; margin-top: 0px;"><b><center>Maschinen</center></b>Lasercutter<br>Kühlung</p>
    <p style="opacity: 0;">.</p>
"]
end 
root_beispielw_fablab ------ root_beispielw_fablab_laser
 
 
end
 
 
root_beispielw ---- root_beispielw_holz_wrapper
subgraph root_beispielw_holz_wrapper["<p style="opacity: 0;">.</p>"]
style root_beispielw_holz_wrapper stroke: none, fill: none
 

subgraph root_beispielw_holz["
    <p style="font-size: 1.5em">Holzwerkstatt</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager Holzwerkstatt<br>👷‍♂️Benutzer Holzwerkstatt</p></p>
    <p style="text-align: left; margin-top: 0px;"><b><center>Maschinen</center></b>Kappsäge<br>Bandschleifer<br>Ständerbohrmaschine</p>
    <p style="opacity: 0;">.</p>
"]
end 

subgraph root_beispielw_holz_bandsaege["
    <p style="font-size: 1.25em">Bandsäge</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager Holzwerkstatt Bandsäge<br>👷‍♂️Benutzer Holzwerkstatt Bandsäge</p></p>
    <p style="text-align: left; margin-top: 0px;"><b><center>Maschinen</center></b>Bandsäge</p>
    <p style="opacity: 0;">.</p>
"]
end 
root_beispielw_holz ------ root_beispielw_holz_bandsaege
 

subgraph root_beispielw_holz_hobelmaschine["
    <p style="font-size: 1.25em">Hobelmaschine</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager Holzwerkstatt Hobelmaschine<br>👷‍♂️Benutzer Holzwerkstatt Hobelmaschine</p></p>
    <p style="text-align: left; margin-top: 0px;"><b><center>Maschinen</center></b>Hobelmaschine</p>
    <p style="opacity: 0;">.</p>
"]
end 
root_beispielw_holz ------ root_beispielw_holz_hobelmaschine
 

subgraph root_beispielw_holz_holzcnc["
    <p style="font-size: 1.25em">CNC-Fräse</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager Holzwerkstatt CNC-Fräse<br>👷‍♂️Benutzer Holzwerkstatt CNC-Fräse</p></p>
    <p style="text-align: left; margin-top: 0px;"><b><center>Maschinen</center></b>CNC-Fräse</p>
    <p style="opacity: 0;">.</p>
"]
end 
root_beispielw_holz ------ root_beispielw_holz_holzcnc
 
 
end
 
 
root_beispielw ---- root_beispielw_siebdruck_wrapper
subgraph root_beispielw_siebdruck_wrapper["<p style="opacity: 0;">.</p>"]
style root_beispielw_siebdruck_wrapper stroke: none, fill: none
 

subgraph root_beispielw_siebdruck["
    <p style="font-size: 1.5em">Siebdruck</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager Siebdruck<br>👷‍♂️Benutzer Siebdruck<br>👩‍🚀Benutzer Druckernutzung</p></p>
    <p style="text-align: left; margin-top: 0px;"><b><center>Maschinen</center></b>SD-Belichter<br>A3-Drucker (👩‍🚀Benutzer Druckernutzung)</p>
    <p style="opacity: 0;">.</p>
"]
end 
 

subgraph filler_root_beispielw_siebdruck_1["
<p style="opacity: 0;">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</p>"]
end
style filler_root_beispielw_siebdruck_1 fill: none, stroke: none
root_beispielw_siebdruck~~~~~~filler_root_beispielw_siebdruck_1

subgraph filler_root_beispielw_siebdruck_2["
<p style="opacity: 0;">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</p>"]
end
style filler_root_beispielw_siebdruck_2 fill: none, stroke: none
filler_root_beispielw_siebdruck_1~~~~~filler_root_beispielw_siebdruck_2
 
 
end
 
 
root_beispielw ---- root_beispielw_textil_wrapper
subgraph root_beispielw_textil_wrapper["<p style="opacity: 0;">.</p>"]
style root_beispielw_textil_wrapper stroke: none, fill: none
 

subgraph root_beispielw_textil["
    <p style="font-size: 1.5em">Textilwerkstatt</p><p style="text-align: left; margin-top: 20px;"><b><center>Rollen</center></b>🛠️Manager Textilwerkstatt<br>👷‍♂️Benutzer Textilwerkstatt</p></p>
    <p style="text-align: left; margin-top: 0px;"><b><center>Maschinen</center></b>Nähmaschine 1<br>Nähmaschine 2</p>
    <p style="opacity: 0;">.</p>
"]
end 
 

subgraph filler_root_beispielw_textil_1["
<p style="opacity: 0;">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</p>"]
end
style filler_root_beispielw_textil_1 fill: none, stroke: none
root_beispielw_textil~~~~~~filler_root_beispielw_textil_1

subgraph filler_root_beispielw_textil_2["
<p style="opacity: 0;">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</p>"]
end
style filler_root_beispielw_textil_2 fill: none, stroke: none
filler_root_beispielw_textil_1~~~~~filler_root_beispielw_textil_2
 
 
end

 

mermaid-diagram-2024-11-06-014627.png

roles.csv

http://docs.fab-access.org/data-fabaccess-config-generator/roles.csv

Tipps

Wer die maschinenliste.csv per CLI bearbeiten oder ansehen möchte, hier ein paar Tipps:

In der Datei settings.ini kann fa_update_dhall = True gesetzt werden, sowie ein Dateifpad fa_dhall_file gesetzt werden. In unsere Konfigurationsdatei bffh.dhall muss dann nur noch das folgende Snippet (siehe https://github.com/elem74/fabaccess-config-generator/blob/main/docs/bffh-vorlage.dhall) eingebunden werden:

---||| GENERATOR START
---||| GENERATOR END

Client benutzen und typische Konfigurationsfehler bei Server und Clients

Nach Download und Installation des Clients kann's endlich praktisch werden!

Der FabAccess Client "Borepin"

Anwendung starten

Der Client sieht beim ersten Öffnen so aus:

Screenshot_20241129-030717_FabAccess.png Screenshot_20241129-025456_FabAccess.png

Mit Server(n) verbinden (Connect to Server)

Das Verbinden mit einem FabAccess BFFH Server kann durch Eingabe des Hosts erfolgen. Entweder ist die Adresse bekannt und wird manuell angegeben oder ein QR-Code (SCAN QR-CODE) wird gescannt. Der QR-Code enthält den Host im Plaintext-Format (keine URN-Syntax wie bei Ressourcen notwendig). Die Hostadresse erlaubt verschiedene, übliche Formate. Zum Beispiel:

Die stabilste Art und Weise der Verbindung kann dir dein FabAccess-Administrator des Vertrauens mitteilen. Wir verbinden uns mit dem Button CONNECT TO SERVER. Das Verbinden dauert in der Regel ein paar Sekunden - je nach Leistungsfähigkeit des Servers, des Clients, des Netzwerks und der Komplexität der Gesamtkonfiguration des Systems.

grafik.png Screenshot_20241129-025716_FabAccess.png

Nach dem Verbinden: Einloggen

Nach erfolgreicher Verbindung zum Server folgt der nächste Screen, der nach Login-Daten fragt. Hier gibt es drei Interaktionsmöglichkeiten:

grafik.png

 Hauptmenü

Ist der Nutzer mit dem Server verbunden, so hat er verschiedene Möglichkeiten der Interaktion. Zum einen kann die Maschinenübersicht (MACHINES) aufgerufen werden. Ist der Nutzer Administrator, so können auch serverweit die Benutzer verwaltet werden (USERS). Außerdem kan das eigene Profil angepasst werden (MY PROFILE). Zudem gibt es eine Übersicht über alle auf dem Endgerät aktuell konfigurierten Serververbindungen (SERVERS: Borepin kann mit mehreren Servern bzw. auch je Server mit verschiedenen Benutzern verbunden werden).

grafik.png

Maschinenübersicht

Die Maschinenübersicht zeigt alle für den Nutzer sichtbaren Geräte (genauer gesagt Ressourcen) an (Berechtigung disclose), getrennt nach Kategorie bzw. Raum, Zone oder Sektor. Die Übersicht zeigt an, welche Geräte vom Benutzer gerade in Benutzung sind (In Use by Me), sowie welche Ressourcen noch frei (Free) oder durch jemand anderen gerade in Benutzung sind (In Use). Die Ansicht aktualisiert sich nicht automatisch - dafür gibt es den Button REFRESH.

Ein praktischer Button ist der große ganz oben: es ist möglich eine Ressource direkt aufzurufen, indem wir einen speziell formatierten QR-Code beispielsweise von einem gedruckten Sticker scannen (SCAN QR-CODE).

Screenshot_20241123-152802_FabAccess.png

Eine Ressource bedienen

Nach Auswahl einer Ressource aus der Maschinenübersicht gibt es verschiedene Handlungen, die ein Nutzer ausführen kann:

In der rechten oberen Ecke ist zudem der aktuelle Zustand der Ressource erkennbar, z.B. Free, In Use, Blocked, Disabled

Eine hilfreiche Information ist außerdem auch die Anzeige, welcher Nutzer die Ressource zuletzt benutzt hat (Last User).

grafik.png

Benutzer verwalten

Dieses Menü obliegt der Rolle des Administrators bzw. Managers und erlaubt das Filtern (Search User), sowie das Anpassen oder Löschen (DELETE) bestehender Nutzer (Rollen zuweisen, Passwort zurücksetzen per FORCE PASSWORD RESET) und das Anlegen neuer Nutzer (ADD NEW USER). Beim Anlegen eines neuen Nutzers können lediglich Benutzername und Passwort konfiguriert werden. Etwaige Keycard-Codes müssen durch den Administrator in der users.toml Konfiguration hinterlegt werden. Das Löschen bestehender Nutzer erfolgt mit Bestätigungsdialog.

grafik.png grafik.png Screenshot_20241129-025834_FabAccess.png Screenshot_20241129-025857_FabAccess.png

Servers - Verbindungen managen

In diesem Menü werden alle Verbindungen (Server + Login) angezeigt, die erfolgreich zum Borepin Client hinzugefügt wurden (Historie). Es wird auch die aktuell genutzte Verbindung angezeigt. Hier kann eine Verbindung ausgewählt und als Standardverbindung gesetzt werden (SET AS DEFAULT CONNECTION). Außerdem können wir uns hier auch vom aktuellen Server abmelden (DISCONNECT) oder eine andere Verbindung zum Verbinden wählen (CONNECT). Selbstverständlich können unbenötigte Verbindungen entfernt werden (DELETE). Das Löschen erfolgt mit Rückvergewisserungsdialog zur Bestätigung des Vorgangs (weiter mit CONFIRM oder abbrechen durch CANCEL).

grafik.png grafik.png Screenshot_20241129-151804_FabAccess.png grafik.png Screenshot_20241129-025437_FabAccess.png

Profil verwalten

Die eigene Profilverwaltung ist relativ übersichtlich. Hier kann lediglich das Passwort neu gesetzt werden. Das Ändern des Namens muss vom Administrator beauftragt werden. Der Benutzer kann seinen Account nicht selbst löschen.

grafik.png

Probleme mit dem Client

"Connection failed. Connection time exceeded."

Ein Typischer Fehler und dieser kann verschiedene Gründe haben. Prüfen:

Screenshot_20241120-012409_FabAccess.png

"Connection failed. Unable to connect to server."

Dieser Fehler drückt aus, dass der Server nicht per DNS auflösbar ist. Hier ist es sinnvoll verschiedene Verbindungskonstellationen durchzuprobieren, zum Beispiel:

Unter Umständen wird der Fehler auch hervorgerufen, weil der Client mit einem VPN-Netwerk verbunden ist und es Konflikte in der DNS-Auflösung oder IP-Adressbereich gibt.

Screenshot_20241120-173332_FabAccess.png

Ich habe die users.toml Datei editiert, aber der oder die Nutzer können sich trotzdem nicht einloggen

Das Editieren ist nur ein Schritt in der Prozesskette! Hast du nach dem Editieren auch einen neuen Import durchgeführt? Erst durch Importieren der users.toml in bffh werden die Änderungen auch wirksam.

Server-Seitige Fehlermeldungen

Fehlkonfigurationen auf dem Server (z.B. wegen fehlenden Ordnern oder Berechtigungen darauf) oder ungeeigneten oder unvollständigen Einstellungen in bffh.dhall führen zu Fehlern beim Start von bffhd.

config::notfound

Error: config::notfound

  × The config file '/etc/diflouroborane.dhall' does not exist or is not readable
  help: Make sure the config file and the directory it's in are readable by the user running bffh

Dieser Fehler tritt dann auf, wenn bffhd ohne Konfigurationsdatei gestartet wird. Verwende den Parameter --config, um eine entsprechende dhall-Datei (z.B. bffh.dhall) anzugeben.

Failed to open socket on 127.0.0.1:59661: Address already in use (os error 98)

ERROR diflouroborane::capnp: Failed to open socket on 127.0.0.1:59661: Address already in use (os error 98)
    at bffhd/capnp/mod.rs:103

Auf dem angegebenen Port und Interface läuft bereits eine (andere) Anwendung. Prüfen Sie, ob bffh bereits läuft oder ein anderer Dienst den gleichen Port für sich beansprucht oder ob Firewall-Einschränkungen dazu führen.

Ermitteln von Anwendungen, die auf dem Port lauschen:

sudo lsof -i tcp:59661
netstat -an | grep LISTEN | grep 59661

failed to initialize state database

Error:
   × failed to initialize state database
   ├─▶ opening the state db environment failed
   ╰─▶ Permission denied

Dieser Fehler drückt aus, dass die Datenbankdatei bffh nicht zum Schreiben geöffnet werden kann. Möglicherweise existiert das Zielverzeichnis nicht oder es gehört dem falschem Benutzer. Prüfen Sie den Schlüssel db_path.

audit log failed

Error:
   × audit log failed
   ╰─▶ Permission denied (os error 13)

Dieser Fehler drückt aus, dass die Datenbankdatei bffh.audit nicht zum Schreiben geöffnet werden kann. Möglicherweise existiert das Zielverzeichnis nicht oder es gehört dem falschem Benutzer. Prüfen Sie den Schlüssel auditlog_path.

error in actor subsystem

   × error in actor subsystem
   ├─▶ MQTT connection failed
   ├─▶ I/O: failed to lookup address information: Name or service not known
   ╰─▶ failed to lookup address information: Name or service not known

Die Verbindung zum MQTT Server konnte nicht hergestellt werden. Der Schlüssel mqtt_url sollte überprüft werden:

failed to initialize TLS config

Error:
   × failed to initialize TLS config
   ├─▶ failed to open certificate file at path examples/cert.pem
   ╰─▶ No such file or directory (os error 2)

Das angegebene SSL Zertifikat konnte nicht gefunden werden. Überprüfen Sie den Schlüssel certfile in bffh.dhall. Existiert die Datei? Hat bffhd Zugriff darauf?

Error:
   × failed to initialize TLS config
   ├─▶ failed to open private key file at path examples/key.pem
   ╰─▶ No such file or directory (os error 2)

Der Private Key für das SSL Zertifikat konnte nicht gefunden werden. Überprüfen Sie den Schlüssel keyfile in bffh.dhall. Existiert die Datei? Hat bffhd Zugriff darauf?

Script-Sammlung

Eine Sammlung verschiedener Scripts über das Basic-Setup hinaus, um dem Admin Dinge zu erleichtern

Script-Sammlung

Backup für die Benutzerdatenbank einrichten

Mit folgendem Backup-Script (bash) können wir die Datenbank sichern. Diese können wir außerdem mit systemd per Service und Timer automatisieren. Alternativ kann das Bash-Script auch als Cronjob eingebunden werden. Folgendes Script sollte je nach Bedarf angepasst werden (Pfade).

Es ist generell sehr wichtig die Benutzerdatenbank regelmäßig zu sichern. Immer dann, wenn ein Administrator bzw. Manager per Client App neue Accounts hinzufügt, Accounts löscht oder Accounts ändert (z.B. Passwortwechsel), dann werden diese Änderungen in einen transienten Zwischenspeicher gelegt. Dieser muss mit der lokalen users.toml zusammengeführt werden. Das erfolgt über den dump-users Parameter.

Wird dieser zusammengeführte Dump nicht ausgeführt und crasht der Serverdienst, dann sind alle etwaigen Remote-Änderungen u.U. nicht wiederbringbar.

Backup Script anlegen und konfigurieren

vim /opt/fabinfra/scripts/bffh-backup.sh
#!/bin/bash

# Database dump command
DB_DUMP_CMD="/opt/fabinfra/bffh/target/release/bffhd -c /opt/fabinfra/bffh-data/config/bffh.dhall --dump-users"

# Backup directory
BACKUP_DIR="/opt/fabinfra/bffh-data/config_backup"
mkdir -p $BACKUP_DIR

# Number of backups to keep
NUM_BACKUPS_TO_KEEP=5

# Dry run flag
DRY_RUN=false

# Parse command-line options
while getopts ":n:r" opt; do
	case $opt in
	n)
		NUM_BACKUPS_TO_KEEP="$OPTARG"
		;;
	r)
		DRY_RUN=true
		;;
	\?)
		echo "Invalid option: -$OPTARG" >&2
		exit 1
		;;
	:)
		echo "Option -$OPTARG requires an argument." >&2
		exit 1
		;;
	esac
done

# Current date and time
CURRENT_DATE=$(date +"%Y%m%d%H%M%S")

# Create a backup file name
BACKUP_FILE="$BACKUP_DIR/db_backup_$CURRENT_DATE.toml"

# Execute the database dump command
if [ "$DRY_RUN" = true ]; then
	echo "Dry run mode: Database backup command will not be executed."
else
	$DB_DUMP_CMD $BACKUP_FILE
fi

# Check if the database dump was successful
if [ $? -eq 0 ]; then
	echo "Database backup completed successfully."

	# Sort backup files by modification time in ascending order
	sorted_backup_files=($(ls -t "$BACKUP_DIR"))

	# Determine number of backups to delete
	num_backups_to_delete=$((${#sorted_backup_files[@]} - NUM_BACKUPS_TO_KEEP))

    cd $BACKUP_DIR

	# Delete oldest backups if necessary
	if [ $num_backups_to_delete -gt 0 ]; then
		for ((i = 0; i < $num_backups_to_delete; i++)); do
			if [ "$DRY_RUN" = true ]; then
				echo "Dry run mode: Would remove old backup: ${sorted_backup_files[$i]}"
			else
				rm "${sorted_backup_files[$i]}"
				echo "Removed old backup: ${sorted_backup_files[$i]}"
			fi
		done
	fi

else
	echo "Error: Database backup failed."
fi
chmod +x /opt/fabinfra/scripts/bffh-backup.sh

Das Script kann einzeln getestet werden. Es kann mit Parametern gestartet werden:

# Trockenlauf (dry run) - nur testen und nichts löschen
/opt/fabinfra/scripts/bffh-backup.sh -r -n 2

# Backup durchführen und nur die letzten 5 aufheben, alle anderen löschen
/opt/fabinfra/scripts/bffh-backup.sh -n 5

Backup-Script mit systemd Timer

Das Script kann als timed Service eingebunden werden, um es so zu automatisieren. Unter Beachtung obiger Parameter in ExecStart kann folgendes eingebunden werden:

vim /opt/fabinfra/scripts/bffh-backup.service
[Unit]
Description=BFFH Backup Service

[Service]
Type=oneshot
ExecStart=/opt/fabinfra/scripts/bffh-backup.sh -n 10

Außerdem als Timer. Dieser muss den gleichen Name haben wie der Service (siehe https://wiki.ubuntuusers.de/systemd/Timer_Units)

vim /opt/fabinfra/scripts/bffh-backup.timer
[Unit]
Description=BFFH Backup Timer

[Timer]
# Run every day at midnight
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Wir aktivieren und starten das Backup schließlich einmal manuell und prüfen dessen Ausgabe.

Hinweis: Da wir einen Timer für den Service verwenden, müssen wir den Service nicht "enablen". Denn das macht der Timer selbst.

sudo ln -sf /opt/fabinfra/scripts/bffh-backup.service /etc/systemd/system/bffh-backup.service
sudo ln -sf /opt/fabinfra/scripts/bffh-backup.timer /etc/systemd/system/bffh-backup.timer
sudo systemctl daemon-reload
sudo systemctl start bffh-backup.service
journal -f -u bffh-backup.service

Backup-Script mit cron

Wer lieber auf einen klassischen Cronjob setzten möchte, kann statt dem Service folgendes machen:

sudo vim /etc/cron.d/bffh-backup
#“At 00:00.”
0 0 * * 0 bffh /opt/fabinfra/scripts/bffh-backup.sh -n 10

Der Cronjob wird um 00:00 Uhr vom Benutzer bffh gestartet. Es gibt keinen Log Output. Dieser lässt sich jedoch leicht ergänzen.

Script-Sammlung

Nutzerdatenbank laden / hashen / prüfen

Nutzerdatenbank laden / hashen

Für das Laden und ggf. Rehashen der Nutzerdatenbank (users.toml) kann folgendes Script genutzt werden. Es prüft zunächst, ob die Konfiguration von bffh valid ist. Wenn nicht, wird nichts unternommen. Im Anschluss werden die Nutzer neu geladen und gleichzeitig wieder exportiert, um etwaige unverschüsselte Passwörter automatisch zu hashen.

Hinweis: Das Script basiert auf einem auf dem System aktiven systemd Service namens bffh.service

mkdir -p /opt/fabinfra/scripts/
vim /opt/fabinfra/scripts/bffh-load-users.sh
#/!bin/bash
#check config
BASE="/opt/fabinfra/bffh/target/release"
DATA="/opt/fabinfra/bffh-data"
CFG="$DATA/config/bffh.dhall"
USERS="$DATA/config/users.toml"

while [[ $# -gt 0 ]]; do
	case $1 in
		-h|--help)
			echo "-h|--help   - show help"
			echo "-r|--rehash - overwrite users.toml with hashed passwords (ensure secure secrets)"
			exit 1
			;;
		-r|--rehash)
			REHASH="y"
			shift
			;;
		-*)
			;;
	esac
done

echo "use -h|--help to show additional script options!"

$BASE/bffhd --check --config $CFG > /dev/null
if [ $? == 0 ]; then
	#pre-check bffh.dhall
	echo "Config is valid. Loading users ..."
	$BASE/bffhd --verbose --config $CFG --load=$USERS

	if [ $? == 0 ]; then
		#then load users.toml
		$BASE/bffhd --verbose --config $CFG --dump-users /tmp/users.toml --force
	else
		echo "Error: Newly given users.toml is invalid!"
		exit 1
	fi

	#if this was successful and service is running, restart it, elso do nothing
	if [ $? == 0 ]; then
		if [[ $REHASH == "y" ]]; then #overwrite users if --rehash option is given (not null)
			echo "Rehasing users.toml!"
			cat /tmp/users.toml > $USERS
			rm /tmp/users.toml
		fi
		FAS="bffh.service"
		STATUS="$(systemctl is-active $FAS)"
		if [ "${STATUS}" = "active" ]; then
			echo "restarting $FAS"
			systemctl restart $FAS
		else
			echo -e "\n\n$FAS not active/existing. Not restarting bffh service!"
		fi
	fi

else
	echo "Error: Currently loaded users.toml is invalid!"
	exit 1
fi
chmod +x /opt/fabinfra/scripts/bffh-load-users.sh

Nutzerdankenbank prüfen

Folgendes Python-Script erlaubt die Auswertung einer users.toml Datei. Es zählt die Nutzer und deren zugewiesene Rollen, validiert eventuell vergebene Cardkeys und gibt Hinweise bei möglichen Datenbankfehlern.

Das Script benötigt mindestens Python 3.11. Erst ab dieser Version wird tomllib unterstützt!

vim /opt/fabinfra/scripts/show-users-toml-stats.py
'''
This script validates users.toml for several aspects
The script requires at least Python 3.11

Written by Mario Voigt (vmario89) - Stadtfabrikanten e.V. - 2024

ToDos
- enter bffh.dhall path to check roles against users.toml. If our toml contains roles, which bffh does not know, we should also warn!
'''

import argparse
import os
import sys
import tomllib
import uuid

'''
cardkeys for FabAccess use Uuid format in Version v4 (see https://docs.rs/uuid/latest/uuid/struct.Uuid.html)
allowed formattings:
- simple: a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8
- hyphenated: a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8
- urn: urn:uuid:A1A2A3A4-B1B2-C1C2-D1D2-D3D4D5D6D7D8
- braced: {a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8}
'''
def is_valid_uuid(val):
    try:
        _uuid = uuid.UUID(val, version=4)
        return True
    except ValueError:
        return False

def main():

    parser = argparse.ArgumentParser()
    parser.add_argument("--db", type=str, help="path of users.toml user database file")
    args = parser.parse_args()
    
    if args.db is None:
        print("Error: no users.toml given. Please add with '--db </path/to/users.toml>'")
        sys.exit(1)

    countUsers = 0
    countUsersWithoutCardkeyOrPassword = 0
    uniqueRoles = []
    countUserWithoutRoles = 0
    countPassword = 0
    countPasswordUnencrypted = 0
    countPasswordEncrypted = 0
    countCardkey = 0
    countCardkeyInvalid = 0
    countUnknownKeys = 0

    countWarnings = 0

    #a definition of valid keys within a user section of FabAccess
    knownKeys = ['roles', 'passwd', 'cardkey']

    usertoml = args.db

    print("{} Checking database {}\n".format("*"*25, "*"*25))

    file_stats = os.stat(usertoml)
    #print(file_stats)
    print("Database size: {} Bytes ({:0.5f} MB)".format(file_stats.st_size, file_stats.st_size / (1024 * 1024)))
    if file_stats.st_size == 0:
        print("Error: File size is zero! Database is corrupted!")
        sys.exit(1)

    print("\n")

    with open(usertoml, "rb") as f:
        try:
            data = tomllib.load(f)
        except Exception as e:
            if "Cannot declare" in str(e) and "twice" in str(e):
                print("Error: found at least one duplicate user. Cannot parse database. Please fix and try again. Message: {}".format(str(e)))
            elif "Invalid value" in str(e):
                print("Error: Some user contains a key without value (e.g. 'passwd = '). Cannot parse database. Please fix and try again. Message: {}".format(str(e)))
            elif "Expected '=' after a key" in str(e):
                print("Error: Found an incorrect key/value mapping. Cannot parse database. Please fix and try again. Message: {}".format(str(e)))
            else:
                print(str(e))
            sys.exit(1)

        for user in data:
            print("--- {}".format(user))

            for key in data[user].keys():
                if key not in knownKeys:
                    print("Warning: User '{}' contains unknown key '{}' (will be ignored by BFFH server)".format(user, key))
                    countWarnings += 1
                    countUnknownKeys += 1

            if "roles" in data[user]:
                roles = data[user]["roles"]
                for role in roles:
                    if role not in uniqueRoles:
                        uniqueRoles.append(role)
                if roles is None: #if role key is defined but empty
                    countUserWithoutRoles += 1
            else: #if role key is not existent
                countUserWithoutRoles += 1

            if "passwd" in data[user]:
                passwd = data[user]["passwd"]
                countPassword += 1
                if passwd.startswith("$argon2") is False:
                    print("Warning: Password for user '{}' is not encrypted!".format(user))
                    countWarnings += 1
                    countPasswordUnencrypted += 1
                else:
                    countPasswordEncrypted += 1

            if "cardkey" in data[user]:
                cardkey = data[user]["cardkey"]  
                if is_valid_uuid(cardkey) is False:
                    print("Warning: Cardkey for user '{}' contains invalid cardkey (no UUID v4)".format(user))
                    countCardkeyInvalid += 1
                    countWarnings += 1

                countCardkey += 1

            if "passwd" not in data[user] and "cardkey" not in data[user]:
                countUsersWithoutCardkeyOrPassword += 1

            countUsers += 1
            print("\n")

        print("\n")

        if countUsers == 0:
            print("Error: Database does not contain any users!")
            sys.exit(1)

        print("{} Database statistics {}\n".format("*"*25, "*"*25))
        print("- Total users: {}".format(countUsers))
        print("- Total unique roles: {}".format(len(uniqueRoles)))
        print("- Total passwords: {} (encrypted: {}, unencrypted: {})".format(countPassword, countPasswordEncrypted, countPasswordUnencrypted))
        print("- Total cardkeys: {}".format(countCardkey))

        print("\n")

        print("{} Important information {}\n".format("*"*25, "*"*25))
        if countUnknownKeys > 0:
            print("- {} unknown keys (will be ignored by BFFH server)".format(countUnknownKeys))

        if countUserWithoutRoles > 0:
            print("- {} users without any roles. They won't be able to do something as client!".format(countUserWithoutRoles))

        if len(uniqueRoles) == 0:
            print("- Globally, there are no roles assigned for any user. They won't be able to do something as client!")

        if countCardkeyInvalid > 0:
            print("- {} invalid cardkeys in your database. They won't be able to authenticate at BFFH server by keycard!".format(countCardkeyInvalid))

        if countUsersWithoutCardkeyOrPassword > 0:
            print("- {} users without both: password and cardkey. They won't be able to login anyhow!".format(countUsersWithoutCardkeyOrPassword))

        if countWarnings > 0:
            print("- {} warnings in total. You might need to optimize your user database!".format(countWarnings))

if __name__ == "__main__":
    main()

Script benutzen:

python3 /opt/fabinfra/scripts/show-users-toml-stats.py --db /opt/fabinfra/bffh-data/config/users.toml



Script-Sammlung

FabAccess State Script (Email-Warnung bei Fehlerzustand)

Ein kleines Helferscript, was an ein beliebiges Mail-Postfach eine Email sendet, wenn unser systemd Service bffh.service nicht mehr korrekt läuft. Falls der Service nicht installiert wurde, dann wird das Script einen Fehler werfen.

Wir verwenden außerdem im Script das smarte smtp-cli Tool von mludvig: https://github.com/mludvig/smtp-cli/tags. Es kann jedoch auch jeder andere beliebige Mail-Client verwendet werden, um cli-basierte Nachrichten zu versenden. Kleiner Fix: https://github.com/mludvig/smtp-cli/issues/28

# smtp-cli installieren
sudo apt install cpanminus
sudo cpanm Net::DNS
sudo apt install libio-socket-ssl-perl libdigest-hmac-perl libterm-readkey-perl libmime-lite-perl libfile-libmagic-perl libio-socket-inet6-perl
cd /opt
sudo wget https://github.com/mludvig/smtp-cli/archive/refs/tags/v3.10.zip
sudo unzip v3.10.zip
sudo rm v3.10.zip

mkdir -p /opt/fabinfra/scripts/
vim /opt/fabinfra/scripts/bffh-state.sh
#/!bin/bash
SMTP_SERVER="smtp.fablabchemnitz.de:587"
SMTP_MAILBOX="REDACTED"
SMTP_PW='REDACTED'
 
FROM="fabaccess.noreply@stadtfabrikanten.org"
TO="webmaster@stadtfabrikanten.org"
SUBJECT="FabAccess BFFH Service failed"
 
MAILFILE="/tmp/mail.txt"
 
systemctl status bffh.service 2>&1 > ${MAILFILE}
if [ $? != 0 ]; then #wenn exit code von systemd unit nicht 0
	cat ${MAILFILE}
    /opt/smtp-cli-3.10/smtp-cli --server ${SMTP_SERVER} --user ${SMTP_MAILBOX} --password ${SMTP_PW} --from "${FROM}" --to "${TO}" --subject "${SUBJECT}" --body-plain ${MAILFILE}
fi
chmod +x /opt/fabinfra/scripts/bffh-state.sh

Und dann testen wir das Script. Es gibt den Status auf der Kommandozeile aus und sollte parallel eine Email an das Zielpostfach mit gleichem Inhalt senden.

Script-Sammlung

DESFire CardKeys generieren

Ein Script zum Erzeugen von passenden DESFire EV2 CardKeys per Kommandozeile (Linux)

mkdir -p /opt/fabinfra/scripts/
vim /opt/fabinfra/scripts/generate-cardkey.sh
#/!bin/bash
hexdump -vn16 -e'4/4 "%08X" 1 "\n"' /dev/urandom
chmod +x /opt/fabinfra/scripts/generate-cardkey.sh

Der Output kann dann in jeder individuellen Nutzersektion mit dem Schlüsselwort cardkey in die users.toml eingebunden werden:

cardkey = "70AFE9E6B1D6352313C2D336ADC2777A"

Siehe hier.

Script-Sammlung

Echtzeituhr (RTC) Modul DS3231 für Raspberry Pi

Wer FabAccess auf einem Raspberry Pi betreiben möchte und eine Echtzeituhr für genaue und unabhängige Zeitstempel wünscht, kann ein RTC Modul installieren und konfigurieren:

Diese Anleitung basiert auf https://learn.adafruit.com/adding-a-real-time-clock-to-raspberry-pi/set-rtc-time

grafik.png

Das von uns verbaute Uhrenmodell ist DS3231

Wir fügen in der Boot-Konfiguration folgende Device Tree Overlays (dtoverlay) ein:

sudo vim /boot/config.txt 

# Additional overlays and parameters are documented /boot/overlays/README
dtoverlay=i2c-rtc,ds3231
sudo vim /etc/modules
i2c-bcm2708
i2c_dev

Danach starten wir den Pi neu

sudo reboot

Nach dem Restart prüfen wir, ob unsere Echtzeituhr verfügbar ist.

i2cdetect -y 1 #should be visible at 0x68

Wenn ja, dann können wir die standardmäßig installierte "Fake Hardware Clock" deinstallieren:

sudo apt remove fake-hwclock
sudo update-rc.d -f fake-hwclock remove
sudo systemctl disable fake-hwclock

Noch etwas nachstellen und dann die Uhr in Betrieb nehmen:

sudo vim /lib/udev/hwclock-set
#!/bin/sh
# Reset the System Clock to UTC if the hardware clock from which it was copied by the kernel was in localtime.
dev=$1
/sbin/hwclock --rtc=$dev --hctosys
sudo hwclock -r
sudo hwclock --verbose -r
sudo hwclock -w #write the time
sudo hwclock -r #read the time