Quantcast
Channel: thomas-leister.de

Turris Omnia 2020 mit OpenWRT am Telekom VDSL-Anschluss betreiben

$
0
0

Seit Ende letzten Jahres bin ich glücklicher Besitzer eines Turris Omnia (ver. 2020) Routers. Das Gerät wurde von der tschechischen CZ.nic entwickelt und unterstützt nicht nur das freie Linux-Betriebssystem OpenWrt, sondern ist selbst auch quelloffene Hardware. Das macht den Turris Omnia ideal für meine Basteleien.

In diesem Beitrag will ich erklären, wie ich den Router an meinen Telekom VDSL2-Anschluss angebunden habe.

Die AVM FritzBox als externes Modem nutzen

Während einige Nutzer den Turris Omnia (TO) hinter einem bestehenden DSL-Router betreiben, habe ich mich dazu entschieden, den Open Source Router so nah wie möglich direkt an meinem Anschluss zu betreiben, um Ärger mit Doppel-NAT und anderen Hindernissen aus dem Weg zu gehen. Erst so entfaltet der Omnia sein volles Potential. “Direkt am Anschluss” soll hier heißen: Die öffentliche IPv4-Adresse soll am Turris Omnia anliegen und auch das öffentliche IPv6-Netz soll hier geroutet werden.

Es gibt verschiedene Möglichkeiten, das Gerät ohne vorgeschaltetes NAT am Anschluss zu betreiben.

  1. Über ein VDSL-SFP-Modul (z.B. ALLNET ALL4781V)
  2. Über ein externes VDSL-Modem mit Bridge-Funktion (z.B. DrayTek Vigor 166)

Der Omnia selbst verfügt über kein DSL-Modem. Da entsprechende SFP-Module ziemlich teuer sind und ich mir auch ein weiteres DSL-Modem als “Bridge” zunächst sparen wollte, habe ich meine FritzBox kurzerhand zu einem reinen DSL-Modem umfunktioniert - auch wenn der Hersteller das nicht offiziell vorsieht. Die FritzBox übernimmt somit nur noch die Übermittlung der Ethernet-Frames über die unteren Protokollschichten des DSL und natürlich die physikalische Übersetzung in entsprechende Signale.

FritzBox Einstellungen in der Expertenansicht. Besonderheit: VLAN-Tagging (Tag 7) wird hier gesetzt,
um es nicht auf dem WAN-Port des TO berücksichtigen zu müssen.

FritzBox Einstellungen in der Expertenansicht. Besonderheit: VLAN-Tagging (Tag 7) wird hier gesetzt,
um es nicht auf dem WAN-Port des TO berücksichtigen zu müssen.

Die FritzBox wird versuchen, eine Internetverbindung ohne Zugangsdaten herzustellen, was selbstverständlich scheitern wird. Daher die Info-LED an der FritzBox rot leuchten und im Systemlog Fehler erscheinen. Leider kenne ich keine Möglichkeit, die LED abzuschalten - auch die “LEDs abschalten” Funktion nützt in diesem Fall nichts.

Wen das stört, ist mit einem reinen DSL-Modem mit Bridge-Funktion wahrscheinlich besser beraten.

Eine Internetverbindung über die FritzBox herstellen

Der WAN-Port des Turris Omnia wird physisch mit einem der LAN-Ports der FritzBox verbunden. Alles, was sich auf den höheren IP-Layern abspielt, läuft nicht mehr auf der FritzBox, sondern auf dem Turris Omnia. Dazu wird via PPPoE eine Tunnelverbindung zur Telekom hergestellt.

Eine PPPoE-Verbindung herstellen

In der Netzwerkkonfiguration /etc/config/network habe ich mein wan und wan6 interface wie folgt konfiguriert:

config interface 'wan'
option ifname 'eth2'
option proto 'pppoe'
option ipv6 'auto'
option username '<zugangsnummer>@t-online.de'
option password '<persönliches Kennwort>'
config interface 'wan6'
option ifname '@wan'
option proto 'dhcpv6'
option reqprefix 'auto'
option reqaddress 'try'

<zugangsnummer> und <persönliches Kennwort> müssen selbstverständlich durch die eigenen Telekom-Zugangsdaten ersetzt werden.

Über option proto 'dhcpv6' und die beiden darauf folgenden Zeilen wird OpenWrt angewiesen, via DHCPv6 eine öffentliche IPv6-Adresse und ein öffentliches /56 IPv6-Netz vom Provider zu beziehen.

Firewall-Regeln für DHCPv6 und ICMP6

Damit der Telekom-Router am anderen Ende regelmäßig Updates zu den von ihm entliehenen IPv4-Adressen, IPv6-Adressen und -Netzen senden kann, werden in der Firewall noch ein paar Ausnahmen für Verbindungen gemacht.

Die folgenden Regeln stammen aus der Default-Konfiguration des Turris Omnia - sie sollten auf eurem System also schon vorhanden sein. Bei mir war das nicht der Fall, sodass ich in einige Problem gelaufen bin, die nicht aufgetreten wären, wenn ich die Firewall in der Vergangenheit einfach so gelassen hätte, wie sie ausgeliefert wurde … ;-)

Hier also der Vollständigkeit halber nochmal alle wichtigen Regeln zum WAN-Interface:

/etc/config/firewall:

# We need to accept udp packets on port 68,
# see https://dev.openwrt.org/ticket/4108
config rule
option name Allow-DHCP-Renew
option src wan
option proto udp
option dest_port 68
option target ACCEPT
option family ipv4
# Allow IPv4 ping
config rule
option name Allow-Ping
option src wan
option proto icmp
option icmp_type echo-request
option family ipv4
option target ACCEPT
config rule
option name Allow-IGMP
option src wan
option proto igmp
option family ipv4
option target ACCEPT
# Allow DHCPv6 replies
# see https://dev.openwrt.org/ticket/10381
config rule
option name Allow-DHCPv6
option src wan
option proto udp
option src_ip fe80::/10
option src_port 547
option dest_ip fe80::/10
option dest_port 546
option family ipv6
option target ACCEPT
config rule
option name Allow-MLD
option src wan
option proto icmp
option src_ip fe80::/10
list icmp_type '130/0'
list icmp_type '131/0'
list icmp_type '132/0'
list icmp_type '143/0'
option family ipv6
option target ACCEPT
# Allow essential incoming IPv6 ICMP traffic
config rule
option name Allow-ICMPv6-Input
option src wan
option proto icmp
list icmp_type echo-request
list icmp_type echo-reply
list icmp_type destination-unreachable
list icmp_type packet-too-big
list icmp_type time-exceeded
list icmp_type bad-header
list icmp_type unknown-header-type
list icmp_type router-solicitation
list icmp_type neighbour-solicitation
list icmp_type router-advertisement
list icmp_type neighbour-advertisement
option limit 1000/sec
option family ipv6
option target ACCEPT
# Allow essential forwarded IPv6 ICMP traffic
config rule
option name Allow-ICMPv6-Forward
option src wan
option dest *
option proto icmp
list icmp_type echo-request
list icmp_type echo-reply
list icmp_type destination-unreachable
list icmp_type packet-too-big
list icmp_type time-exceeded
list icmp_type bad-header
list icmp_type unknown-header-type
option limit 1000/sec
option family ipv6
option target ACCEPT

Konfiguration der lokalen Netzwerke

Das /56 Netz wird nun in kleinere Teilnetze (/64) aufgeteilt und auf die internen Interfaces verteilt. In meinem Fall auf das lan Netzwerk und das SRV Netzwerk und seine Interfaces:

config interface 'lan'
option type 'bridge'
option proto 'static'
option ipaddr '192.168.1.1'
option netmask '255.255.255.0'
option ip6ifaceid '::1'
option ip6assign '64'
option ip6hint '01'
[...]
config interface 'SRV'
option type 'bridge'
option proto 'static'
list ipaddr '192.168.3.1/24'
option netmask '255.255.255.0'
option ip6ifaceid '::1'
option ip6assign '64'
option ip6hint '03'
[...]

Dabei mache ich mir die “IPv6 hinting” Funktionalität zunutze, denn standardmäßig würfelt OpenWrt das letzte Oktett der beiden /64 Netze aus. Ich habe aber eine konkrete Vorstellung, mit welchen Hex-Ziffern meine Netze enden sollen, und weise OpenWrt an, sich nach der Benennung der entsprechenden IPv4-Netze zu richten.

Mein lan Netz soll dementsprechend auf 01 enden und mein SRV Netz auf 03.

2003:c6:4f12:20::/56 (Vom Anbieter delegiertes Netz)
2003:c6:4f12:2001::1/64 => 01 als letztes Oktett des /64 Netzes für br-lan
2003:c6:4f12:2003::1/64 => 03 als letztes Oktett des /64 Netzes für br-SRV

Die entsprechenden Einstellungen sind in den Interfacekonfigurationen hinterlegt (ip6hint).

Einstellungen aktivieren

Neue Einstellungen einchecken:

uci commit network
uci commit firewall

Und Services neu starten:

service firewall restart
service network restart

Nach dem Anwenden der Einstellungen sollte sich der TO nun bei der Telekom authentifizieren, nach einigen Momenten IP-Adressen bekommen und den Clients in den Hausnetzwerken die jeweiligen /64 Netze anbieten. Die Geräte generieren sich daraufhin selbst IPv6-Adressen und sind dann in der Lage, via IPv6 und IPv4 ins Internet zu kommunizieren.

(für IPv4 ist zwichen dem WAN-Interface und den internen Interfaces standardmäßig ein NAT eingerichtet. Auf IPv4 wurde in diesem Beitrag nicht weiter eingegangen, weil es in den meisten Fällen bereits konfiguriert ist und keine zusätzlichen Schritte notwendig sind.)

Noch ein Hinweis zum Schluss: Sentinel-Minipot

Das sentinel-minipot Paket stellt auf dem Turris Omnia (falls gewünscht) einen sog. Honeypot dar (Siehe: Sentinel). Also einen Server, auf dem HTTP, FTP, Mail- und Telnet Dienste laufen, und der potentielle Angreifer verraten soll. Ich empfehle, den Dienst (falls auf eurem Turris aktiviert) zu deaktivieren, weil sonst das Telekom-Monitoring Alarm schlägt:

Sehr geehrter Herr Leister,

zu Ihrem Internetzugang haben wir Hinweise auf Sicherheitslücken oder Konfigurationsfehler erhalten, die einen Angriff auf Ihr System und/oder einen Missbrauch Ihres Systems für Angriffe auf Dritte ermöglichen. Ersteres gefährdet gegebenenfalls die Integrität und Authentizität Ihrer Daten.

Die folgende IP-Adresse war Ihrem Anschluss an dem genannten Zeitpunkt zugeordnet: IP-Adresse: 80.xxx.xxx.xxx
Zeitpunkt: 12.02.2021 14:03:07 MEZ
Offener Dienst: telnet (23)

Weitere Angaben: banner: Username:

Wir empfehlen Ihnen, Ihren Router / Endgeräte / Netzwerkgeräte so zu konfigurieren, dass nicht relevante Ports oder Dienste geschlossen sind. Weiterhin prüfen Sie bitte, ob eine neuere Firmwareversion vorhanden ist. Diese sollten Sie gegebenenfalls einspielen.

Auch alle anderen Security-Checks, die von extern auf Netzwerksicherheit prüfen, schlagen völlig berechtigt Alarm, wie z.B. der C’t Netzwerkcheck: https://www.heise.de/security/dienste/Netzwerkcheck-2114.html

Deaktivieren lässt sich der Honeypot einfach über das Turris Foris Webinterface, oder auf der Kommandozeile via

service sentinel-minipot stop
service sentinel-minipot disable

Danach sollte Ruhe sein.


Hilfreiche Links:


Problemlösung: Asymmetrisches Routing durch Wireguard VPN

$
0
0

Für die Bereitstellung von öffentlichen IPv6-Adressen in nicht-IPv6-Netzwerken und zur Absicherung meiner Kommunikation in fremden Netzwerken betreibe ich ein eigenes kleines, Wireguard-basiertes VPNWährend des Testbetriebs stellte sich heraus, dass ich zwar mit IPv6-Zieladressen im Internet ohne Probleme kommunizieren konnte - doch die Kommunikation zu IPv6-Ressourcen, die innerhalb meiner eigener Infrastruktur liegen, schlug fehl. Genauer: TCP-basierte Kommunikation schlug fehl. Nach etwas Debugging mittels tcpdump und einer Skizze meiner Infrastruktur war die Ursache schnell klar:

Ohne es gleich zu bemerken, wurden TCP-Anfragen an meine eigenen Services anders geroutet als die dazugehörigen Antworten. Im Folgenden will ich das Problem und mögliche Lösungen erklären.

Eines vorweg: Das Problem ist ein relativ spezifisches und tritt u.U. nur dann auf, wenn VPN-Server und angesprochener Service (in meinem Fall Nextcloud) im selben Netz-Segment betrieben werden.

Sehen wir uns zunächst einmal die Netzstruktur an:

Der betreffende Teil meiner Serverinfrastruktur befindet sich hinter einer zentralen Firewall “FW”. Er besteht zum einen aus dem Wireguard VPN-Server, der ein öffentliches IPv6-Subnetz an die Clients verteilt - und meinem Nextcloud Server, der ebenso wie das primäre Netzwerinterface meines VPN-Servers im selben Subnetz hängt.

Wenn Ressourcen im Internet angesprochen werden, die außerhalb meiner eigenen Infrastruktur liegen, sieht der Weg der TCP-Requests (rot) und TCP-Responses (grün) wie folgt aus:

Symmetrische Routen
Symmetrische Routen

Man spricht hier von symmetrischem Routing: Der Weg der Anfrage und der Antworten sind (zumindest innerhalb meines Setups) identisch - beide Male ist die Firewall involviert. Sie protokolliert die ausgehende Verbindung (rot) und legitimiert daraufhin die eingehende Verbindung (grün) für eine Antwort.

Etwas anders sieht es aus, wenn ich meinen Nextcloud-Dienst anspreche: Weil sich der VPN-Server im selben Subnetz befindet wie der Nextcloud- erver, wird die Anfrage zum Nextcloud Server nicht über das default-Gateway / die Firewall geschickt, sondern auf dem direkten Wege über das LAN. Der Nextcloud Server beobachtet eine eingehende Verbindung aus einem IP-Adressbereich, der nicht seinem eigenen entspricht (… dem IPv6 VPN Bereich) und antwortet daraufhin nicht wieder direkt auf dem LAN, sondern schickt die Antwort über die Firewall. Diese hat allerdings die ursprüngliche Anfrage nicht mitbekommen und blockiert die “herrenlose” Antwort ohne zugehörige Anfrage. Die Antwort kommt also nie an.

Asymmetrische Routen
Asymmetrische Routen

Kern des Problems ist, dass der Weg der Anfrage ein anderer als der der Antwort ist und die Sicherheitsmechanismen der FW anspringen.

Zur Lösung eignen sich drei Wege - ich habe mich für den letzten entschieden:

  1. Auf dem Nextcloud Server: Antworten auf Anfragen aus dem VPN-Adressbereich nicht über die FW routen, sondern direkt über das LAN bzw zum VPN-Server.
  2. Auf der FW: Das Verhalten der FW ändern und auch Antworten durchlassen, für die keine Anfrage protokolliert wurde
  3. Auf dem VPN-Server: Anfragen auch dann über die Firewall leiten, wenn sie an ein Ziel im lokalen Netzsegment gehen.

Lösung #1 ist unpraktisch, weil dazu nicht nur der Nextcloud Server angepasst werden muss, sondern auch alle weiteren lokalen Server, die potenziell über das VPN angesprochen werden. Funktioniert, ist aber nicht “schön”.

Lösung #2 gefällt nicht, weil hierzu ein Sicherheitsmechnismus der Firewall deaktiviert werden muss.

Lösung #3 bringt zwar ein paar Umwege mit sich - im lokalen Netzwerk spielen diese bzgl. Performance allerdings eine untergeordnete Rolle und fallen bei mir nicht ins Gewicht. Daher habe ich mich für diese Lösung entschieden.

Asymmetrische Routen
Asymmetrische Routen

Implementierung

Wie schon im verwandten Beitrag “Network settings for second Wireguard-based default gateway” habe ich auf dem VPN-Server eine weitere Routingtabelle angelegt:

echo "101 rt_wg6" >> /etc/iproute2/rt_tables

Meine Wireguard-Konfiguration habe ich um einige PostUp und PostDown Definitionen ergänzt:

# Route traffic to services on LAN via another routing table
# to avoid asymmetric shortcut via LAN instead of FW.
Table = off
PostUp = ip -6 route add default via 2001:DB8:2::1 dev ens3 table wg6
PostUp = ip -6 route add 2001:DB8:1::/50 dev wg6 table wg6
PostUp = ip -6 rule add from 2001:DB8:1::/50 table wg6
PostDown = ip -6 route del default via 2001:DB8:2::1 dev ens3 table wg6
PostDown = ip -6 route del 2001:DB8:1::/50 dev wg6 table wg6
PostDown = ip -6 rule del from 2001:DB8:1::/50 table wg6

… wobei

  • 2001:DB8:1::/50 das VPN-Netz ist und
  • 2001:DB8:2::1 die IP-Adresse der Firewall

Aus dem VPN lassen sich nun sowohl interne Server als auch externe problemlos erreichen.

LoRaWAN Bodenfeuchtesensor mit Raspberry Pi Pico

$
0
0

Motivation

Anlass für die Entwicklung eines Bodenfeuchtesensors war die Avocadopflanze, die meine Freundin im Sommer 2021 aufgezogen hat. Durch die Nähe zum Fenster hatte diese zwar ausreichend Licht - gleichzeitig wurde die Erde im Pflanzentopf aber auch zügig ausgetrocknet. Da wir beide keinen besonders grünen Daumen haben, musste - zumindest aus meiner Sicht - eine technische Lösung dafür her: Ein Sensor, der den Feuchtigkeitsgehalt der Erde misst und mich alarmiert, wenn der Wert unter einen gewissen Schwellwert sinkt.

Die Ziele waren schnell abgesteckt:

  • Aktive Benachrichtigungen über aktuellen Zustand des Bodens, sobald ein kritischer Wert erreicht wird
  • Versand der Benachrichtigungen via XMPP, sodass diese via Smartphone empfangen werden können
  • Mögliche Abfragen der aktuellen Bodenfeuchtigkeit via Messenger-Chat (Chatbot)
  • Hohes Maß an Energiesparsamkeit der Bodeneinheit, um einen langen Akkubetrieb zu ermöglichen

Dazu kommen natürlich noch weichere Ziele wie z.B. die Einhaltung eines Hobby-freundlichen Budgets und eine gewisse Sensorkompaktheit, um das Gerät unauffällig am Pflanzentopf anbringen zu können.

Architektur

Kommen wir zur Architektur:

Architekturzeichnung mit Systembestandteilen

Die Komponenten in der Reihenfolge des Datenflusses der Sensordaten:

LoRaWAN Sensoreinheit

Die Sensoreinheit hat nur eine Aufgabe: Feuchtigkeit messen und übermitteln. Kein Umrechnen, kein Einstufen - einfach nur übertragen. Sie befindet sich in Topfnähe und soll von einem Akku mit Strom versorgt werden. Damit der Energieverbrauch niedrig bleibt, habe ich mich für eine Kombination aus einem Raspberry Pi Pico und einem Waveshare LoRaWAN Modul SX1262 (EU-Band) entschieden. Durch LoRaWAN ist es möglich, mit geringstem Energieeinsatz sehr kleine Datenmengen (einige Bytes) in regelmäßigen Abständen auch über weite Strecken zu übertragen. In einigen Fällen sogar 2 km weit oder mehr. Mögliche Datenrate und Reichweite sind von vielen Faktoren abhängig, doch für meine Zwecke absolut ausreichend - wenn nicht sogar überdimensioniert.

Mir ging es aber vor allem um den geringen Energiebedarf. Ansonsten hätte sich für den Einsatz in der Wohnung vermutlich auch Bluetooth LE, Zigbee oder ähnliches geeignet.

Raspberry Pi Pico mit Waveshare LoRa Modul

Die Wahl der Microcontrollerplattform ist auf das Raspberry Pi Pico gefallen, weil ich das Board mit dem RP2040 ARM Cortex M0+ bereits vor einiger Zeit ins Auge gefallen ist: Es ist klein, simpel, ohne Schnickschnack, lässt sich mit handelsüblichen Tools bedienen und braucht keine Sonderlocken, merkwürdige IDEs oder sonst etwas, was mich stören würde. Zudem ist es einigermaßen energiesparsam und kompakt, sodass man es gut am Topf in einem kleinen Gehäuse unterbringen kann.

Das Waveshare LoRaWAN Modul ist speziell für das Raspberry Pi Pico entwickelt worden und lässt sich mittels Pin-Header ganz einfach aufstecken. Sogar eine kleine LoRa Antenne und ein kleiner Akku (eher als Pufferakku anzusehen) sind in der Lieferung enthalten.

Die eigentliche Feuchtigkeitsmessung geschieht durch einen kapazitiven Bodenfeuchtesensor des Typs “Capacitive Soil Moisture Sensor v2.0”. Ein Hersteller oder eine genauere Modellnummer ist mir nicht bekannt, aber eine Websuche nach dem Titel sollte zum korrekten Sensor führen.

Ich habe mich aus einem bestimmten Grund für einen kapazitiven (und keinen resistiven!) Sensor entschieden: Die grundlegend unterschiedliche Funktionsweise verringert Korrosion am Sensor, weil im Vergleich zu einem resistiven Sensor keine offenliegenden Kontaktflächen benötigt werden. Während der resistive Sensor effektiv Strom durch das Erdreich leitet und seinen elektr. Widerstand misst, nutzt der kapazitive Sensor die Erd-/Messumgebung als elektr. Kondensator und misst seine kapazitiven Eigenschaften über Signallaufzeiten. Genauso funktionieren übrigens die meisten Füllstand-/Tanksensoren in Fahrzeugen. Mehr zur Funktionsweise und den Vorteilen erfahrt ihr in diesem Video: #207 Why most Arduino Soil Moisture Sensors suck (incl. solution) - YouTube

Der Sourcecode für die Sensoreinheit ist unter der MIT Lizenz auf GitHub verfügbar: https://github.com/ThomasLeister/plantmonitor-sensor

Capacitive Soil Moisture Sensor v2.0

LoRaWAN Gateway

Damit die LoRa Daten von der Sensoreinheit weiterverarbeitet werden können, werden sie von einem LoRaWAN Gateway aufgenommen und via MQTT an das “The Things Network” (TTN) weitergelietet. Das TTN ist ein weltweites Netzwerk, das durch verschlüsselte Daten aus LoRa-Sensoren gefüttert wird. Diesem Netzwerk kann man mit seinem eigenen Gateway (oder durch ein Gateway Fremder) beitreten und kommt so über verschiedenste Online-Interfaces wieder an seine Sensordaten, z.B. via HTTP, MQTT oder andere. Ich habe mich für das beliebte MQTT Protokoll entschieden, weil es für den Zweck perfekt geeignet ist. Es erfreut sich insbesondere in der IoT-Branche wachsender Beliebtheit und dient dazu, Sensorwerte auszutauschen oder Aktoren anzusteuern.

LoRaWAN Gateways können aufgrund der patentierten Funktechnik und des wenig Consumer-orientierten Marktes sehr teuer sein. Man befindet sich schnell in der 300 € Preisklasse, wenn man sich nach professionellen Gateways umsieht.

Viele Hobbybastler greifen daher zu einfachen sog. “Single-Channel Gateways” zurück. Das sind im Grunde nur einfache LoRa “Clienteinheiten”, wie sie auch auf meinem Waveshare Modul für das Raspberry Pi Pico zum Einsatz kommen. Eine simple Kommunikation auf nur einem einzigen der vielen parallel genutzten LoRa Kanälen ist damit möglich. Allerdings entspricht so ein Betrieb nicht der LoRa Spezifikation und bringt einige Limitierungen mit sich, weshalb mittlerweile vom Betrieb eines solchen Single-Channel Gateways abgeraten wird.

Nachdem ich mich einige Tage lang wenig erfolgreich mit so einer “Frickellösung” herumgeschlagen habe, war mir der Griff in die Tasche doch lieber: Für ca 80 € kann man das TTN Indoor Gateway erstehen, das speziell für nichtprofessionelle Betreiber gedacht zu sein scheint: Es entspricht der LoRa Spezifikation und ist bereits passend für das TTN vorkonfiguriert, sodass man das Gateway nur noch im TTN Dashboard registrieren muss.

TTN - The Things Network

Wie bereits erwähnt, bietet das TTN verschiedene Schnittstellen an, um Sensordaten wieder abzurufen. Dabei können die empfangenen Daten im TTN auch gleich gefiltert oder “umsortiert” / segmentiert werden.

Da die Payload einer LoRa Nachricht nur wenige Bytes groß sein darf (abhängig von der Sendewiederholfrequenz und dem Abstand zum Gateway), bleibt nicht viel Platz für eine Segmentierung der Daten oder ein Labeling. Mit anderen Worten: Statt einen JSON String als Payload zu verschicken, schickt man nur einzelne, rohe Datenbytes. Im Fall meines Sensors: Zwei Datenbytes, um den ADC-Wert des Feuchtesensors abzubilden.

Mittels Filterscript in der TTN Cloud können die zusammengeketteten Bytes zum Beispiel getrennt, neu geordnet oder umgerechnet werden. Ergebnis ist eine JSON-Struktur, die die Daten übersichtlich und menschenlesbar abbildet.

In meinem Fall musste die Byte-Reihenfolge architekturbedingt umgekehrt werden. Das zugehörige Javascript-Snippet sieht so aus:

function decodeUplink(input) {
return {
data: {
moisture_raw: input.bytes[0] | (input.bytes[1] << 8)
},
warnings: [],
errors: []
};
}

TTN Screenshot

Der Plantmonitor

Kommen wir zum Herzstück meiner Entwicklungsarbeit: Dem “Plantmonitor” Backend.

Was langweilig klingt (Namensfindung ist ein Problem!), beinhaltet den meisten selbst entwickelten Programmcode in diesem Projekt. Der Plantmonitor ist ein in Go geschriebener Daemon, der folgende Aufgaben übernimmt bzw. folgende Funktionen hat:

  • Neue Sensorwerte via MQTT aus dem Things Network empfangen
  • Sensorwerte normalisieren und mittels Kalibrierung in Prozentwerte umrechnen
  • Sensorwerte quantisieren und den Zustand der Erde anhand einer Grenzwertdefinition ermitteln (“Erde feucht”, “Erde trocken”, “Erde nass”, …)
  • Bei einem kritischen Zustand einen festgelegten Empfängerkreis via XMPP-Nachricht alarmieren
  • Erinnerungen bei länger anhaltenden, kritischen Zuständen versenden
  • Bei fehlenden Sensorupdates Alarm schlagen
  • Auf Zustandsabfragen der Nutzer via XMPP-Chat freundlich antworten :)

Nach anfänglichen Schwierigkeiten mit Fehlalarmen funktioniert der Plantmonitor nun wie gewünscht. Er ist als Open Source Projekt unter der MIT-Lizenz frei verfügbar: https://github.com/ThomasLeister/plantmonitor

Zum Plantmonitor-Backend gibt es einen gesonderten Artikel mit ausführlicheren Informationen zum Aufbau und zur Funktionsweise des Quantisierers:

Ein Quantisierer mit Hysterese für meinen LoRaWAB Bodenfeuchtesensor

XMPP-Server und Client

Weil trashserver.net sowieso schon da ist und ich mit meinem Umfeld hauptsächlich via XMPP und “Conversations” am Smartphone kommuniziere, war das XMPP-Protokoll für mich die erste Wahl bei der Überlegung, wie ich benachrichtigt werden wollte.

Für Go gibt es auch schon verschiedene XMPP-Libraries, die sich sehr einfach ansteuern lassen. Das Anbinden eines XMPP-Servers ist somit kein großer Aufwand und schnell erledigt. Ich habe mich übrigens für die Go-Library go-xmpp der Ejabberd-Entwickler “ProcessOne” entschieden: https://github.com/FluuxIO/go-xmpp

XMPP Konversations mit meiner Pflanze “Fritz”

Das XMPP-Modul meines Plantmonitor Backends ist in der Lage, abhängig von der Art der Nachricht (Warnung, Entwarnung, Info) und der Bodenfeuchtigkeit aus verschiedenen vordefinierten Nachrichten zufällig zu wählen und passende GIFs aus der Giphy-Sammlung mitzuschicken. So bringt der Bot ein bisschen Abwechslung ins Spiel. ;-)

Ausblick

Obwohl es aus meiner Sicht noch nicht ganz fertig ist, ist mein Pflanzenüberwachungssystem schon im Einsatz und verrichtet seinen Dienst. Für folgende Features / Verbesserungen ist bereits eine Umsetzung angedacht:

  • Akkubetrieb:
    Die Sensoreinheit wird aktuell noch über ein USB-Netzteil betrieben. Angedacht war eigentlich ein Akkubetrieb, doch die Energiesparmechanismen der Raspberry Pi Pico Plattform sind noch nicht vollumfänglich verstanden. Die Implementierung erweiterter Energiesparmechanismen steht auf meiner To-Do Liste. Dann ist auch ein Akkubetrieb (mit Ladestandsüberwachung) möglich.

  • Gehäuse für die Sensoreinheit:
    Derzeit liegt die Elektronik der Sensoreinheit noch offen. Ich habe vor, mir ein passendes Kunststoffgehäuse zu besorgen oder mir eines zu entwerfen und mittels 3D-Drucker ausdrucken zu lassen.

  • Unterstützung mehrerer Pflanzensensoren im Plantmonitor Backend:
    Derzeit wird nur ein Sensor / die Überwachung einer Pflanze unterstützt. Während die Software schon teilweise auf den Betrieb mehrerer Pflanzen ausgelegt ist, fehlen noch einige Änderungen, um einen Parallelbetrieb mehrerer Sensoren zu unterstützen. Ich begnüge mich vorerst mit der Unterstützung eines einzelnen Sensors, bis das Projekt weiter gereift ist. So vermeide ich unnötige Komplexität schon zu Beginn meines Projekts.

Ein Quantisierer mit Hysterese für meinen LoRaWAN Bodenfeuchtesensor

$
0
0

Wie in diesem Artikel vorgestellt, habe ich einen LoRaWAN Sensor entwickelt, der mich über kritische Bodenfeuchtigkeit z.B. einer Zimmerpflanze benachrichtigt. Auf ein Modul meines selbst entwickelten Backends zur Auswertung will ich in diesem Beitrag näher eingehen: Den Quantisierer mit Hysterese.

Damit das Plantmonitor-Backend bei kritischen Bodenzuständen (zu wenig Feuchtigkeit, zu viel Feuchtigkeit) Alarm schlagen kann, müssen die Sensordaten korrekt eingeordnet werden. Dazu nutze ich einen Quantisierer, der Messwerte anhand einer vorher festgelegten Konfiguration in unterschiedliche “Level” bzw. Zustände einteilt. So werden Feuchtigkeitswerte von 0-64 beispielsweise dem Zustand “low” zugeordnet. Höhere Werte zwischen 65 und 92 sorgen hingegen für den Zustand “normal”.

Das funktioniert soweit - doch in der Praxis kann so ein einfacher Quantisierer schnell für Frust sorgen. Denn nicht alle Sensoren liefern streng monoton verlaufende Wertereihen bzw. kurzfristig aussagekräftige Daten - so auch mein kapazitiver Feuchtigkeitssensor. In so einem Fall kann eine Hysterese Abhilfe schaffen.

Wozu eine Hysterese?

Eine Hysterese sorgt dafür, dass für einen ansteigenden Wert ein anderer Schwellwert für den Übertritt in einen anderen Zustand gilt, als für einen sinkenden. Veranschaulichen lässt sich das am besten an einem Beispiel:

Beispielgrafik: Zustandsübergänge ohne Hysterese

Hier ist ein einfacher Quantisierer ohne Hysterese zu sehen. Für alle Werte kann einer der Zustände “low”, “normal” oder “high” zugeordnet werden. Befindet man sich mit dem Sensorwert nahe eines Zustandsübergangs, kann es durch leichte Veränderungen des Sensorwerts zum “flapping” kommen. Liegt der aktuelle Sensorwert bei 65, würde ein weiteres Absinken der Feuchtigkeit auf 64 einen Zustandswechsel auf “low” bedeuten. Meldet der Sensor bei der nächsten Messung durch seine leichte Toleranz wieder eine 65, würde erneut ein Zugangswechsel registriert und eine weitere Benachrichtigung gesendet. Durch die Austrocknung der Erde wird der Sensor im Laufe der Zeit erneut einen Zustandswechsel richtung “low” auslösen - erneut würde eine Benachrichtigung gesendet.

Dieses störende Verhalten lässt sich durch eine Hysterese in gewissem Rahmen eliminieren. Kennt man die Fehlertoleranz des Sensors in etwa und wählt die Übergangsbereiche der Hysterese groß genug, sind Zustandsübergänge “robuster” und tendieren weniger dazu, zu “wackeln”.

Beispielgrafik: Hysterese Schema

Hier wird der Zustandsübergang durch aufsteigende (rot) und absteigende (grün) Sensorwerte veranschaulicht: Während der Schwellwert zum nächsthöheren Zustand nach oben verschoben wird, wird der Schwellwert für einen Wechsel zu einem niedrigeren Zustand nach unten bewegt. Die Zustandsübergänge werden “sticky”. Erst, wenn ein Sensorwert in verlässlichen Wertebereichen eines Zustands liegt, wird ein erneuter Zustandswechsel ermöglicht. Bis es soweit ist, wird der alte Zustand beibehalten - auch, wenn rein rechnerisch bereits der nächstgelegene Zustand erreicht wurde.

Im Beispiel von oben würde also ein Absinken des Sensorwertes von 65 auf 64 noch keinen Zustandswechsel bedeuten. Erst bei einem Wert unter 63 würde ein Zustandswechsel auf “low” ausgelöst werden. Steigt der Wert durch Messtoleranzen erneut auf 65, bedeuten kleine Steigerungen ebenso noch keinen Zustandswechsel: Erst, wenn der Wert 66 übertroffen ist, wird der Wechsel auf den Zustand “normal” vollzogen. Für aufsteigende und absteigende Werte gelten also unterschiedliche Schwellwerte - je nach Richtung.

Implementierung der Hysterese

Für eine einfache Implementierung einer solchen Hysterese genügt es, die Grafik von oben leicht verändert darzustellen und sich Folgendes bewusst zu machen:

Grafik: Vereinfachung Hysterese

  • Die Wertebereiche der Zustände überlappen an den Schnittstellen
  • Wo es keine Überlappung gibt, kann ein eindeutiger Zustand zugeordnet werden.
  • Liegt ein Sensorwert in einem überlappenden Bereich (“ambivalence”), kommen prinzipiell zwei Zustände für eine Zustandszuordnung infrage. Hier gelten dann folgende Regeln:
    • Entspricht einer der Zustände dem vorherigen Zustand des Systems, soll das System in diesem Zustand verharren (=> “stickyness” der Hysterese)
    • Entspricht keiner der beiden Zustände dem vorherigen Zustand des Systems (beispielsweise durch eine sehr starke Wertänderung oder weil das System noch keine Historie hat und es keinen vorherigen Wert gibt), sollen die Überlappungen entfernt werden und eine Einstufung aufgrund eines herkömmlichen Modells ohne Hysterese stattfinden (siehe erste Grafik).

Diese Regeln lassen sich ohne viel Aufwand direkt in Code übertragen. Abschließende Unit-Tests sorgen in meinem Fall dafür, dass das korrekte Verhalten auch in Grenzbereichen und bei untypischen Wertentwicklungen sichergestellt ist.

Nutzen

Mithilfe meine Hysterese-Implementierung konnte ich die Zahl der unnützen Fehlalarme drastisch reduzieren, denn der von mir verwendete Sensor schwankt durchaus ein paar mal hin- und her, bevor ein Wert für eine Weile stabil bleibt. Wer will, kann sich die Implementierung im quantifier Modul meines Plantmonitors hier ansehen:

https://github.com/ThomasLeister/plantmonitor/tree/master/quantifier

Rock64 Mini NAS Upgrade (2022)

$
0
0

Nachdem mein Mini NAS auf Basis eines Pine Rock64 Single Board Computers (SBC) 4 Jahre lang gute Dienste verrichtet hat, war es an der Zeit, dem Setup ein Upgrade zu verpassen. Das NAS wird mittlerweile nicht nur mehr nur für Backups genutzt, sondern auch ein kleiner DNLA-Medienserver im lokalen Netzwerk. Daher war es mir wichtig, das NAS so umzubauen, dass

  1. Die Datensicherheit gewährleistet ist, also keine Daten durch Fehler verfälscht werden oder verloren gehen
  2. Mehr Speicherplatz zur Verfügung steht

Ich habe mich entschieden, die eine SSD mit einem Terabyte Speicherplatz durch zwei SSDs mit jeweils 2 TB Speicherplatz zu ersetzen und diese in einem RAID-1 Verbund laufen zu lassen. Als Dateisystem sollte ZFS eingesetzt werden. Für beide SSDs sollte ein ansehliches Gehäuse gefunden werden, das ich neben mein transparentes und halboffenes Rock64 Acrylgehäuse stellen kann.

Das Rock64 auf dem neuen SSD-Gehäuse
Das Rock64 auf dem neuen SSD-Gehäuse

ZFS als Dateisystem

ZFS nutze ich auf den großen Servern schon einige Jahre und bin begeistert von dessen Featureset und der Zuverlässigkeit. Besonders schön: Mit wenigen Kommandos übernimmt ZFS nicht nur die Rolle eines klassischen Dateisystems (mit speziellen Funktionen, wie z.B. Snapshots), sondern auch die Funktion eines Volume-Managers (Z.b: LVM) und eines RAID-Managers (MD). Und weil das noch nicht genug ist, übernimmt es in meinem Fall auch die Verschlüsselungs-Funktion, die man üblicherweise LUKS anvertraut. Alles in einem zu haben, gibt mir ein gutes Gefühl. Ich bin kein Fan von mehreren Schichten und Komplexität, wo sie nicht sein muss.

SSDs und Gehäuse

Bei den SSDs habe ich mich für die von mir geschätzten und bekannten Crucial BX 500 (2 TB) entschieden. Ich habe sowohl mit den MX500 als auch mit den BX500 langjährige, gute Erfahrungen gemacht - auch im RAID-Verbund und mit ZFS. Die BX500 unterscheiden sich maßgeblich in ihrer Schreibfestigkeit und der Garantie - hier liegen die MX500 leicht vorn. Da ich auf dem NAS aber ohnehin nicht exzessiv viel schreiben werde, habe ich mir einige Euro gespart und zu den BX500 gegriffen. Bevor ich die `Totel Terabytes Written" erreicht haben werde, werde ich vermutlich schon ein neues NAS System zusammengebaut oder zumindest mehr Speicherbedarf haben.

Oh - und warum überhaupt SSDs und nicht viel günstigere HDDs? Darum:

  • SSDs sind schnell
  • SSDs sind robust
  • SSDs brauchen wenig Energie
  • SSDs sind leise
  • HDDs kommen mir antik vor. Ja, vielleicht nicht zu recht, aber da bin ich eitel.

Das Gehäuse - ein IcyBox “RAID Gehäuse für 2x HDD/SSD mit USB 3.1” hat mich gleich begeistert, weil es nicht nur gut aussieht und kompakt ist, sondern mit USB 3.1 auch eine schnelle Anbindung bietet. Da die beiden SSDs im RAID laufen, wird die USB Schnittstelle doppelt belastet. Mit USB 2 würde man hier sehr schnell an unangenehme Grenzen stoßen. USB 3.1 wird zwar von Rock64 noch nicht untersützt, aber zumindest kann ich auf USB 3.0 zurückgreifen und mich freuen, dass mein Gehäuse zukunftsfähig ist … falls das Rock64 Board einmal gegen ein stärkeres ausgetauscht wird.

Die eingebaute RAID-Funktionalität des Gehäuses interessiert mich übrigens nicht besonders. Ich nutze das Gehäuse nur im “Single” Betrieb. Die beiden SSDs erscheinen für das Betriebssystem also als zwei voneinander unabhängige Laufwerke. Für die RAID-Funktionalität vertraue ich auf ZFS sehr viel mehr, als auf ein günstiges Festplattengehäuse. Zudem dürfte sich eine Wiederherstellung schwierig gestalten, wenn der integrierte RAID Controller einmal Schaden nehmen sollte …

Das Rock64 mit neuem SSD-Gehäuse im 19" Rack
Das Rock64 mit neuem SSD-Gehäuse im 19" Rack

Ein Armbian-Update

Mein Mini NAS läuft seit jeher mit Armbian. Beim ersten Anstecken der SSDs gab es jedoch einen Schreckmoment: Während die SSDs an meinem Laptop tadellos als zwei Blockdevices erkannt wurden, passierte am Rock64 USB 3.0 Anschluss nichts. Am USB 2.0 Anschluss hingegen wurden die SSDs erkannt.

Da sowieso ein Armbian-Upgrade auf eine neuere Version anstand und für diese Version auch noch keine ZFS-Pakete bereitstanden, nutzte ich die Gelegenheit zu einem Armbian-Upgrade - und siehe da: Nun wurden auch die SSDs korrekt erkannt. Glück gehabt!

ZFS Installieren

Die Installation von ZFS auf einer neuen Armbian-Version gestaltet sich sehr einfach (wenn auch etwas zeitintensiv). Da ZFS - anders als die anderen üblichen Dateisysteme - aus Lizenzgründen nicht im Linux-Kernel enthalten ist, muss es “out of tree” als Kernelmodul gebaut werden. Das nimmt einem das zfs-dkms Paket ab, welches über die Armbian-Paketquellen bereitsteht. Damit der Kompiliervorgang erfolgreich durchgeführt werden kann, müssen zuerst die Linux-Headerdateien für Armbian installiert werden. Die Installation führt über armbian-config:

$ armbian-config
Dann "Software" > "Install Headers"

Schließlich können endlich die ZFS-Pakete installiert werden:

ZFS Pakete installieren:

apt install zfsutils-linux zfs-dkms

Im Zuge der Paketinstallation wird ein dynamisches Kernelmodul für ZFS gebaut - also nicht wundern, wenn der Schritt “Building initial module for 5.15.72-rockchip64” einige Minuten dauert. Dem Rock64 geht dabei ziemlich die Luft aus …

Nach der Installation kann mittels zfs list nachgesehen werden, ob der Dateisystemtreiber korrekt in den Kernel integriert ist. Erscheint keine Fehlermeldung, ist ZFS bereit. Andernfalls muss das Rock64 evtl. neu gestarter werden.

Verschlüsselten RAID-1 ZFS Pool erzeugen

Nun ist es Zeit, sich um die Verschlüsselung zu kümmern: Persönliche Daten auf dem NAS sollen auch nach der zukünftigen Entsorgung meiner SSDs privat bleiben. Hierzu will ich die SSDs per default Verschlüsseln (wobei sich aber für gewisse ZFS Volumes auch Ausnahmen definieren lassen).

Die Entschlüsselung geschieht ohne weiteres Zutun beim Booten über eine Key-Datei, die im Root-Filesystem des Rock64 (der SD-Karte) liegen. Die Schlüsseldatei wird wie folgt erzeugt und abgelegt:

mkdir /var/lib/zfs/
tr -dc A-Za-z0-9 </dev/urandom | head -c 16 > /var/lib/zfs/raid2tb.key
chmod 600 /var/lib/zfs/raid2tb.key

Es empfiehlt sich, die Schlüsseldatei kurz mittels cat auszugeben, um sich den Schlüssel in einem Passwortmanager wegzusichern. Geht diese Datei bzw. ihr Inhalt verloren, können die auf den SSDs befindlichen Daten nicht wiederhergestellt werden.

Nun wird der ZFS-Pool generiert: Wie schon beschrieben - mit RAID-1 und aktiver Verschlüsselung (per default).

Pool erstellen:

zpool create \
-o feature@encryption=enabled \
-O encryption=on \
-O atime=off \
-O keylocation=file:///var/lib/zfs/raid2tb.key \
-O keyformat=passphrase \
raid2tb mirror \
/dev/disk/by-id/ata-CT2000BX500SSD1_2237E6655828 \
/dev/disk/by-id/ata-CT2000BX500SSD1_2237E665585F

(Achtung, erste -o Option is klein geschrieben!) - atime=off schaltet “access time” feature ab

Damit die automatische Entschlüsselung beim Boot funktioniert, wird eine neue systemd-Servicedatei angelegt unter /etc/systemd/system/zfs-load-key.service:

[Unit]
Description=Load encryption keys
DefaultDependencies=no
After=zfs-import.target
Before=zfs-mount.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/zfs load-key -a
StandardInput=tty-force
[Install]
WantedBy=zfs-mount.service

Dann wird der neue Service aktiviert:

systemctl daemon-reload
systemctl enable zfs-load-key

ZFS Volumes erstellen

In meinem Fall soll es zunächst zwei ZFS Volumes und daher zwei Mountpoints geben: Ein Volume für Backup-Daten und ein zweites als Medienspeicher für den DLNA-/Medienserver.

mkdir -p /mnt/raid2tb/backup
mkdir -p /mnt/raid2tb/media

ZFS Datasets anlegen:

zfs create \
-o mountpoint=/mnt/raid2tb/backup \
raid2tb/backup
zfs create \
-o mountpoint=/mnt/raid2tb/media \
raid2tb/media

Automatisch erstellten Mountpoint für Stamm-Dataset entfernen (wird nicht benötigt):

zfs set mountpoint=none raid2tb

Die ZFS Volumes werden automatisch nach dem Erzeugen an den Mountpoints gemountet und sind einsatzbereit.

Performance mit Verschlüsselung

SSDs im Mirror-Betrieb (RAID-1) und verbunden durch USB 3.0 versprechen hohe Geschwindigkeiten beim Lesen und Schreiben, oder?

“Rohes” Lesen von nur einer Festplatte - ohne Beachtung des Dateisystems:

hdparm -tT --direct /dev/sda
/dev/sda:
Timing O_DIRECT cached reads: 628 MB in 2.00 seconds = 313.31 MB/sec
Timing O_DIRECT disk reads: 976 MB in 3.01 seconds = 324.77 MB/sec

Das sieht schon mal ganz ordentlich aus! Definitiv mehr, als ich über die Gigabit-Ethernet Schnittstelle des Rock64 übertragen kann (brutto theoretisch max. 120 MB/s). Hier haben wir also kein Problem.

Deutlich trauriger sieht die Situation aber aus, wenn ich eine Datei vom verschlüsselten ZFS Dataset auslese:

Lesen:

fio --rw=read --name=test --size=1G --direct=1
Run status group 0 (all jobs):
READ: bw=9184KiB/s (9404kB/s), 9184KiB/s-9184KiB/s (9404kB/s-9404kB/s), io=1024MiB (1074MB), run=114175-114175msec

Schreiben:

fio --rw=write --name=test --size=1G --direct=1
Run status group 0 (all jobs):
WRITE: bw=10.6MiB/s (11.2MB/s), 10.6MiB/s-10.6MiB/s (11.2MB/s-11.2MB/s), io=1024MiB (1074MB), run=96264-96264msec

Nur 9.4 MB/s beim Lesen und 11.2 MB/s beim Schreiben? Wie kann das sein? Die - zugegeben - sehr ernüchternde Performance wird durch die Verschlüsselung verursacht. Wie sich später herausstellte, beherrscht OpenZFS die ARM AES-Instuktionen für die hardwarebeschleunigte Verschlüsselung noch nicht und greift daher auf eine Software-basierte Verschlüsselung zurück. Diese ist Größenordnungen langsamer, als wir es von HW-beschleunigter Verschlüsselung kennen.

EIn Rückschlag, den ich nicht erwartet habe. Es gibt aber etwas Trost, denn die bereits aktivierte Verschlüsselung lässt sich bei Bedarf für einzelne ZFS Volumes auch ausschalten. Zum Beispiel für mein “media” Volume, bei dem ich sehr gut auf Verschlüsselung verzichten kann. Denn ob nun jemand in der Lage ist, Fernsehserien, Filme oder Musik aus meinen SSDs zu extrahieren, ist mir relativ egal.

Verschlüsselung wieder ausschalten

Für das “media” Dataset soll die Verschlüsselung also abgeschaltet werden. Allerdings muss dazu ein neues Dataset ohne Verschlüsselung erstellt werden, denn die Verschlüsselung kann nicht einfach abgeschaltet werden. Einmal beim Generierten eingeschaltet, bleibt sie für ein Dataset immer aktiv. Daher benennen wir das alte Dataset einfach um und erzeugen ein neues ohne Verschlüsselung:

zfs rename raid2tb/media raid2tb/media-old

Mountpoint des alten Datasets ändern:

zfs set mountpoint=/mnt/raid2tb/media-old raid2tb/media-old

Neues Dataset ohne Verschlüsselung erstellen:

zfs create -o encryption=off -o mountpoint=/mnt/raid2tb/media raid2tb/media

Daten verschieben (falls vorhanden):

mv /mnt/raid2tb/media-old/* /mnt/raid2tb/media/

Performance ohne Verschlüsselung

Nun nochmal ein Performancetest ohne Verschlüsselung:

Lesen:

fio --rw=read --name=test --size=1G --direct=1
[...]
Run status group 0 (all jobs):
READ: bw=123MiB/s (128MB/s), 123MiB/s-123MiB/s (128MB/s-128MB/s), io=1024MiB (1074MB), run=8357-8357msec

Schreiben:

fio --rw=write --name=test --size=1G --direct=1
[...]
Run status group 0 (all jobs):
WRITE: bw=61.5MiB/s (64.5MB/s), 61.5MiB/s-61.5MiB/s (64.5MB/s-64.5MB/s), io=1024MiB (1074MB), run=16657-16657msec

128 MB/s und 64.5 MB/s. Das ist zwar immer noch weit vom theoretischen Maximum der SSDs entfernt (irgendwo bei ca 500 MB/s), allerdings kann man hiermit schon gut arbeiten. Beim Lesen wäre ohnehin das Ethernet der Flaschenhals und auch mit der Schreibrate von nur 61 MB/s kann ich mich arrangieren, wenn ich bedenke, dass die meisten meiner Dateiübertragungen über das SSH Protokoll laufen und dadurch sowieso schon auf ca. 26 MB/s limitiert sind (ähnliches Problem: Auch bei SSD kommt das Rock64 mit der SSH-Verschlüsselung kaum hinterher).

Die geringe Schreib-/Leserate bei verschlüsselten Datasets bleibt ernüchternd. Hier muss ich wohl auf einen Entwickler warten, der die ZFS Cryptofunktionen fit für ARM Prozessoren macht. Ob daran ein dringendes Interesse besteht, darf aber bezweifelt werden. Solange muss ich mit der eher mäßigen Performance leben oder auf eine andere Lösung umschwenken. Da wäre zum Beispiel noch LUKS, welches auf ARM besser performt _(… und welches ich eigentlich vermeiden wollte, weil es Dinge wieder komplexer macht … *grummel*).

Fazit und Ausblick

Ich habe gemischte Gefühle. Auf der Positiv-Seite stehen die nun aktivierte Verschlüsselung, die Ausfall- und Datensicherheit durch ein RAID-1 und die Möglichkeit, mittels ZFS Snapshots von meinen Dateien anlegen zu können. Alles mittels ZFS realisieren zu können, gefällt mir. Außerdem verfüge ich nun über mehr Speicherplatz und habe die SSDs in einem schicken neuen Gehäuse untergebracht, das in meinem Rack positiv auffällt und die LED-Lightshow bereichert.

Ein negativer Beigeschmack bleibt wegen der miesen ZFS Performance auf verschlüsselten Datasets. Ich kann vorerst damit leben, weil in der Kette zwischen Laptop und Speicher auch noch andere limitierende Faktoren hängen; zum Beispiel das WLAN oder SSH-Verbindungen. Auf längere Sicht will ich die Performance aber verbessern. Sei es durch ein HW-Upgrade (es gibt da vielversprechende Rock64 Alternaiven und Nachfolger), oder durch eine SW-Änderung. Vielleicht habe ich ja Glück und HW AES-SUpport für ARM wird von ZFS schon bald unterstützt. Das dazugehörige GitHub Ticket ist jedenfalls auf der Watchlist.

Jellyfin Mediaserver auf meinem Rock64 Mini NAS

$
0
0

Durch einige Posts auf Mastodon und ein Video von Jeff Geerling bin ich auf die Multimediaserver-Software Jellyfin aufmerksam geworden. Mir war das Konkurrenzrodukt Plex bereits bekannt, aber mit Jellyfin bekommt man eine reine FOSS Lösung, bei der man sich keine Gedanken um Lizensierung oder Kosten machen muss. Für meine Ansprüche - vor allem Musikstreaming - sollte Jellyfin genügen, daher habe ich es kurzerhand auf meinem Rock64-basierten Mini NAS installiert.

Die Jellyfin Albumansicht für Musik
Die Jellyfin Albumansicht für Musik

Setup

Jellyfin kann als Debian-Paket oder Container installiert werden. Bei den Containerumgebungen kann man aus Docker und Podman wählen. Da Jellyfin für mich neu ist und ich den Betrieb erst einmal erproben will, habe ich mich für ein Podman-basiertes Setup entschieden. Auf Docker verzichte ich gerne, wenn es geht. Das modernere Podman mit seinen Features ist mir grundsätzlich sympathischer - aber das ist eine andere Geschichte und soll hier keine weitere Rolle spielen.

Zunächst wird Podman inkl. zweier Abhängigkeiten von den Ubuntu/Armbian-Paketquellen installiert:

apt install podman uidmap slirp4netns

Danach wird ein neuer User angelegt, unter dem der Jellyfish-Container später ausgeführt werden soll. Mit Podman können Container ohne Daemon und ohne Root-Rechte als einfacher Benutzer ausgeführt werden. Von diesem Sicherheitsvorteil will ich selbstverständlich profitieren:

adduser --disabled-password --home /opt/jellyfin jellyfin
su - jellyfin

(ein “normaler” Login dieses jellyfin Users wird mittels --disabled-password ausgeschlossen. Somit muss man kein Passwort vergeben und sich auch keine Gedanken um einen weiteren Angriffsvektor über einen Benutzeraccount machen. Der pseudo-Login wird über su ausgeführt.)

Im Homeverzeichnis des neuen Benutzers (/opt/jellyfin) wird ein neues Bash-Script “init-jellyfin.sh” angelegt. Dieses dient nur dem ersten Start (und weiteren Starts nach einer Konfigurationsänderung). Nach dem ersten Start wird das Script aber idR. nicht mehr benutzt, sondern auf den systemd-basierten Autostart des Podman-Containers zurückgegriffen. Inhalt der Scriptdatei:

podman run \
--detach \
--label "io.containers.autoupdate=registry" \
--name jellyfin \
--publish 8096:8096/tcp \
--rm \
--user $(id -u):$(id -g) \
--userns keep-id \
--volume jellyfin-cache:/cache:Z \
--volume jellyfin-config:/config:Z \
--mount type=bind,source=/mnt/raid2tb/media,destination=/media,ro=true \
docker.io/linuxserver/jellyfin:latest

(Pfad /mnt/raid2tb/media ggf. anpassen und zu eigenem Medienbibliothek-Pfad ändern!)

Die Datei wird nun ausführbar gemacht und gestartet:

chmod u+x create-jellyfin.sh
./create-jellyfin.sh

Im Hintergrund werden die Containerimages heruntergeladen und die darin befindlichen Applikationn gestartet. Das kann auf einem SBC wie dem Rock64 einige Momente dauern. Wird die Kommandozeile ohne Fehlermeldung wieder freigegeben, scheint alles funktioniert zu haben. Im Webbrowser sollte dann unter http://<server.ip>:8096 das Webinterface zu sehen sein.

systemd Service für Autostart erstellen

Mit den aktuellen Einstellungen wird der Container nach einem Server-Neustart nicht mehr automatisch gestartet und müsste händisch mittels

podman start jellyfin

neu gestartert werden. Das können wir mit einem systemd User-Service ändern.

Systemd erlaubt es, benutzerspezifische Services anzulegen und zu starten. Während der Jellyfin-Container im Hintergrund noch läuft, können wir über ein spezielles Podman-Kommando automatisch einen passenden systemd Service generieren lassen. Das geht so:

mkdir -p ~/.config/systemd/user/
podman generate systemd jellyfin > ~/.config/systemd/user/jellyfin.service

Im nächsten Schritt würde man den neuen Service normalerweise mittels systemctl --user enable jellyfin aktivieren. Da wir zu Beginn einen jellyfin Account ohne Passwort angelegt und uns mittels su als dieser neue Benutzer eingeloggt haben, muss zunächst noch eine Korrektur vorgenommen werden.

Würde man an dieser Stelle das genannte systemd Kommando ausführen, erschiene die Fehlermeldung

Failed to connect to bus: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined

Das kommt daher, dass wir uns mit dem jellyfin User nicht regulär via SSH oder ein TTY eingeloggt haben. Deshalb wurde der systemd user Service für den Benutzer nicht aktiviert und er verfügt über keine gültige Konfiguration des DBus-Zugriffs. Mit den folgenden Zeilen und Kommandos kann das Problem beseitigt werden:

In ~/.profile am Ende hinzufügen:

# This is for enabling "systemctl --user" commands without valid login session
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"

Nun zum root User wechseln (z.B. durch exit) - dann als root User ausführen:

loginctl enable-linger $(id -u jellyfin)
systemctl start user@$(id -u jellyfin).service

Zurück zum jellyfin User (su - jellyfin) - und der Service wird aktiviert:

systemctl --user daemon-reload
systemctl --user enable --now jellyfin.service

Das sollte nun ohne Fehlermeldungen funktionieren. Solltet ihr eine zsh Shell verwenden, muss noch mittels source ~/.profile die .profile Datei gesourced werden. Alternativ lassen sich die oben genannten export Zeilen selbstverständlich auch in die ~/.zshrc Datei unten einbetten.

WebUI: Medienbibliotheken einrichten

Alles weitere wird im Webfrontend von Jellyfin eingerichtet. Das Initialsetup wird euch zu euren Wunschbibliotheken befragen. Hier gilt es zu beachten, dass der Medienpfad als Pfad innerhalb des Containers angegeben wird - und das ist idR. immer /media. Egal, wie der Pfad zu euren Medien außerhalb des Containers lauten mag. Die beiden Pfade wurden im init-jellyfin.sh Script aufeinander gemapped.

Extra: Webinterface mit Nginx Proxy

Wer seine Jellyfish-Instanz auf Port 80 und / oder über eine sichere HTTPS Verbindung bereitstellen will, kann sich von der Beispielkonfiguration unter https://jellyfin.org/docs/general/networking/nginx.html inspirieren lassen.

Die Einrichtung wird vor allem dann empfohlen, wenn ihr außerhalb des heimischen Netzwerks mit Jellyfin kommunizieren wollt. So umgeht ihr auch elegant Portsperren für nicht-HTTP Ports in Hotel WLANs und sorgt dafür, dass niemand euren Traffic belauscht.

Ein Wort zur Performance

Wunder darf man von einem kleinen Single Board Computer wie dem Rock64 nicht erwarten. Sobald der Medienscan abgeschlossen ist, reagiert die Weboberfläche zwar nicht raasend schnell, aber angemessen zügig. Was die Live-Transcodierung von Videos angeht, wird man aber enttäuscht: Wie sich herausstellt, bringt Jellyfin zwar alles mit, was es zum Hardware-Kodierung braucht, soch das Rock64 Board ist dafür nicht geschaffen und kann verschiedene Mediencodecs bestenfalls in Hardware decodieren - nicht aber codieren.

Wenn höher aufgelöste Filme gestreamt werden sollen und das Zielgerät das Format nicht direkt unterstützt, stößt man schnell an Grenzen. Der Filme-Fan greift also lieber zu einem x86-basierten Computer mit einem halbwegs starken Prozessor, der die fraglichen Codecs wie H264 und H265 sowie VP9 in Hardware codieren kann.

Da ich vorhabe, fast ausschließlich Musik zu streamen, fällt dieser Nachteil für mich nicht besonders ins Gewicht.

Die Jellyfin Startseite
Die Jellyfin Startseite

Restic REST-Server installieren

$
0
0

Restic lässt sich wunderbar mit einer Reihe verschiedener Speicher und Speicherzugriffsprotokolle verwenden: SFTP, S3, Rclone, … Die für mich interessanteste ist dabei jedoch die REST-Schnittstelle, über die mit dem Restic-Server kommuniziert werden kann. Restic-Server wird dabei auf einem NAS oder anderen Server installiet und bildet das Backend. Eine Restic-Instanz auf einem zu sichernden Client kann sich zu Restic-Server verbinden und Daten sicher und vor allem sehr performant in den Speicher schreiben. Neben der Möglichkeit, ein “append only” Repository zu betreiben, heben die Entwickler vor allem die bessere Performance z.B. im Vergleich zu SFTP hervor:

Compared to the SFTP backend, the REST backend has better performance, especially so if you can skip additional crypto overhead by using plain HTTP transport (restic already properly encrypts all data it sends, so using HTTPS is mostly about authentication). But, even if you use HTTPS transport, the REST protocol should be faster and more scalable, due to some inefficiencies of the SFTP protocol … - https://github.com/restic/rest-server/blob/master/README.md

Die Installation eines solchen Restic Rest-Servers ist schnell erledigt:

Systembenutzer einrichten und Restic REST-Server herunterladen

Als root einloggen, neuen Systemuser erstellen:

adduser --system restic-server

Unter https://github.com/restic/rest-server/releases den neuesten Release suchen und Datei für System herunterladen, z.B. für ARM-basierte Linux Systeme:

wget https://github.com/restic/rest-server/releases/download/v0.11.0/rest-server_0.11.0_linux_arm64.tar.gz
tar xzf rest-server_0.11.0_linux_arm64.tar.gz
cp rest-server_0.11.0_linux_arm64/rest-server /usr/local/bin/restic-server

Das heruntergeladene Binary ausführbar machen:

chmod +x /usr/local/bin/restic-server

Und den Restic REST-Server testweise starten:

restic-server --version

Das Backupverzeichnis vorbereiten

Meine Backups sollen im Verzeichnis /mnt/raid2tb/backup liegen. Es besteht die Möglichkeit, mehrere Nutzer authentifiziert auf den Server zugreifen zu lassen, deshalb habe ich für meinen persönlichen Account einen Unterordner thomas angelegt. Der Verzeichnisname entspricht dem Benutzernamen bei der Authentifizierung.

mkdir -p /mnt/raid2tb/backup/thomas
chown -R restic-server /mnt/raid2tb/backup

Zugang für einen neuen Restic Server Benutzer erstellen:

apt install apache2-utils
cd /mnt/raid2tb/backup
htpasswd -B -c .htpasswd thomas
chown -R restic-server /mnt/raid2tb/backup/.htpasswd
chmod 600 /mnt/raid2tb/backup/.htpasswd

(thomas durch eigenen Benutzernamen ersetzen!)

Dieses Passwort gilt nicht für die Restic Verschlüsselung des Backups, sondern nur für den Zugang zum Restic-Server bzw. dessen HTTP-Schnittstelle. Die Verschlüsselung des Backups ist davon unabhängig. Achtet darauf, dass der Benutzername im htpasswd Befehl genau dem Verzeichnisnamen entspricht, denn nur so kann der Restic-Server Benutzernanmeldung und Backupverzeichnis einander zuordnen.

Nginx Proxy

Damit der Traffic zum Backupserver verschlüsselt wird, soll eine TLS-Transportverschlüsselung auf der Verbindung zwischen Client und Restic-Server verwendet werden. Der Restic-Server kann zwar über das --tls Flag auch schon selbst mit SSL-Sicherheitszertifikaten umgehen - da ich aber bereits einen Nginx-Server auf meinem NAS betreibe, um meinen Jellyfin Service auszuliefern, soll in meinem Fall Nginx das Zertifikatshandling übernehmen.

Eine passende Nginx-Konfiguration sieht so aus:

server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name nas.meinserver.tld;
root /var/www/default;
ssl_certificate /etc/tls/nas.meinserver.tld/fullchain.pem;
ssl_certificate_key /etc/tls/nas.meinserver.tld/privkey.pem;
access_log off;
error_log off;
client_max_body_size 1G;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
location / {
proxy_pass http://[::1]:8000;
}
if ($ssl_protocol = "") {
return 301 https://$server_name$request_uri;
}
}

Die Konfiguration wird nach /etc/nginx/sites-available/restic-server.conf geschrieben und aktiviert:

ln -s /etc/nginx/sites-available/restic-server.conf /etc/nginx/sites-enabled/restic-server.conf
systemctl reload nginx

Systemd Service anlegen

Einen systemd Service anlegen unter /etc/systemd/system/restic-server.service

[Unit]
Description=Restic Server
After=syslog.target
After=network.target
[Service]
Type=simple
User=restic-server
ExecStart=/usr/local/bin/restic-server --path /mnt/raid2tb/backup --private-repos
Restart=always
RestartSec=5
StandardOutput=syslog
StandardError=syslog
[Install]
WantedBy=multi-user.target#

Service aktivieren und starten:

systemctl daemon-reload
systemctl enable restic-server
systemctl start restic-server

Mittels systemctl status restic-server kann überprüft werden, ob der Server läuft:

[...]
Active: active (running)
[...]
systemd[1]: Started Restic Server.

Verbindung mit dem Client

Auf dem Client wird der Server über die Umgebungsvariable RESTIC_REPOSITORY eingestellt. In meinem Beispiel mit HTTP und Nginx als Web-Proxy sieht die URL wie folgt aus:

RESTIC_REPOSITORY="rest:https://thomas:thomaspasswort@nas.meinserver.tld/thomas"

Dabei werden Benutzername und Passwort durch einen Doppelpunkt getrennt. Der Teil nach dem @-Zeichen richtet sich nach Art des Setups. In meinem Fall mit HTTPS Support und einem vorgeschalteten Nginx nenne ich einfach die Subdomain meines NAS. Ohne den Nginx muss ggf. der Port noch genannt werden, z.B. nas.meinserver.tld:8000.

DLNA und andere SSDP Dienste über lokale Subnetze hinweg nutzen (OpenWRT)

$
0
0

Zur besseren Trennung zwischen persönlichen Geräten (Laptop, PC), IoT Geräten und Servern setze ich Zuhause einen OpenWRT Router ein, der für die Gerätegruppen einzelne Subnetze aufspannt. So können Laptops beispielsweise auf alle anderen Dienste und Subnetze zugreifen, während Server sich nicht zum Laptop verbinden können uns in ihrem Netzwerk eingesperrt bleiben. Einzig der Internetzugang ist für sie freigegeben. Das Setup lässt mich Nachts ruhiger schlafen, bringt aber auch Nachteile mit sich: Einige der Dienste, die man in seinem Heimnetzwerk betreibt, sind für diese Auftrennung nicht konzipiert und funktionieren nicht auf Anhieb. So auch DLNA, über welches Medienserver ihre Inhalte beispielsweise an ein TV-Gerät freigeben können. Hinter DLNA verbirgt sich eigentlich eine ganze Gruppe von Protokollen und Standards. Ein wichtiges Protokoll im DLNA-Standard ist SSDP (Simple Service Discovery Protocol).

Das Problem: SSDP - Simple Service Discovery Protocol

SSDP ist ein Broadcast-basiertes Protokoll und wird im DLNA-Kontext dazu genutzt, einen DLNA-Client oder DLNA-Server im Netzwerk bekannt zu machen. Schließlich soll der Fernseher ja Bescheid wissen, a) dass ein DLNA-Server existriert und b) wie er ihn erreichen / ansprechen kann. Beide Informationen werden in Form von Broadcast-Paketen im Broadcast-IP-Bereich 239.255.255.250 (IPv4) bzw. ff02::c (IPv6) verkündet, sodass jedes Gerät im selben Netzsegment diese Informationen mitlesen und ggf. nutzen kann. Diese Broadcast-Nachrichten sind idR. auf das aktuelle Subnetz limitiert. Sie werden also nicht geroutet und somit nicht an andere Subnetze weitergegeben.

Hier liegt das Problem: Befindet sich der Fernseher also im IoT-Netz 192.168.4.0/24 und der DLNA-Server im Server-Subnetz 192.168.3.0/24, werden diese normalerweise die gegenseitigen SSDP-Broadcastpakete nicht empfangen und sich somit nicht gegenseitig erkennen können.

Die Lösung: Broadcast-Routing mit smcroute

Zum Glück gibt es eine Lösung für das Problem: Den “Static Multicast Routing Daemonsmcroute. Der Daemon routet dabei nicht selbst Pakete, sondern sorgt durch eine korrekte Konfiguration des Linux-Kernels dafür, dass zwischen definierten Subnetzen und Geräten Broadcastpakete ausgetauscht werden.

Broadcast vs. Multicast: Von Broadcast spricht man in einem Netzwerk, wenn alle Mitglieder dieses Netzwerks die entsprechenden Pakete mitlesen können. Macht man diese Pakete nur für bestimmte Teilnehmer oder für weitere Subnetze verfügbar, spricht man von Multicast. Da in smcroute die Quellen und Ziele konkret definiert werden (müssen) und Pakete über Netzgrenzen hinweg transportiert werden, wird der Begriff “Multicast Routing Daemon” verwendet.

Nach der Installation des smcroute Pakets kann der Daemon in der Konfigurationsdatei unter /etc/smcroute.conf konfiguriert werden. Hier verbirgt sich u.U. schon eine vorausgefüllte Beispieldatei, welche ich durch folgende ersetzt habe:

# Enable bridge interfaces for smcroute use
phyint br-SRV enable
phyint br-lan enable
phyint br-IOT enable
# Allowed routing for IPv4
mgroup from br-SRV source 192.168.3.2 group 239.255.255.250
mroute from br-SRV source 192.168.3.2 group 239.255.255.250 to br-IOT br-lan
mgroup from br-IOT group 239.255.255.250
mroute from br-IOT group 239.255.255.250 to br-lan br-SRV
mgroup from br-lan group 239.255.255.250
mroute from br-lan group 239.255.255.250 to br-SRV br-IOT
# Same for IPv6
mgroup from br-SRV source fd00:0:0:3::2 group ff02::c
mroute from br-SRV source fd00:0:0:3::2 group ff02::c to br-IOT br-lan
mgroup from br-IOT group ff02::c
mroute from br-IOT group ff02::c to br-lan br-SRV
mgroup from br-lan group ff02::c
mroute from br-lan group ff02::c to br-SRV br-IOT

Die Konfiguration bewirkt, dass Multicast-Pakete zwischen den Netzwerkbrücken (und Interfaces) br-lan, br-IOT und br-SRV ausgetauscht werden, wenn sie in den von SSDP genutzen Broadcast-Adressbereich fallen.

(durch source 192.168.3.2 bzw. source fd00:0:0:3::2 gilt für Pakete aus dem SRV-Netz die zusätzliche Einschränkung, dass nur Pakete von meinem Jellyfin-Server aus diesem Netz geroutet werden dürfen)

Der smcroute Daemon muss danach neu gestartet werden:

service smcroute restart

Dabei kann es hilfreich sein, nach dem Start auch das Systemlog /var/log/messages im Blick zu behalten, um Fehler bei der Konfiguration zu erkennen.

Man könnte meinen, jetzt sollten sich DLNA-Server und -Clients bereits finden können. Wäre da nur nicht …

Der Showstopper: Die TTL (Time-to-Live)

Da Broadcast-Pakete (eigentlich!) das eigene Subnetz nicht verlassen sollen, haben sie eine Time-To-Live von 1. Das bewirkt, dass solche Pakete vom Router nicht weiter für das Routing in andere Netze in Betracht gezogen werden. Eigentlich eine vernünftige Sache - doch in unserem Fall soll ja gerade dies erreicht werden. Die TTL muss also angehoben werden, damit SSDP Broadcast-Pakete “es über den Router hinaus schaffen” können. Die Anpassung kann an verschiedenen Orten erfolgen

  1. Zum einen kann das Paket an der Quelle, z.B. DLNA-Server, eine höhere TTL verpasst bekommen ..
  2. … oder in der Prerouting-Queue des OpenWRT-Routers, bevor es aufgrund seiner TTL von 1 verworfen wird.

Weg 2) kann zentral verwaltet werden und an den Clients sind keine Änderungen nötig. Das ist Vorteilhaft, weil ich die TTL der DLNA-Pakete, die mein Fernseher schickt, ohnehin nicht erhöhen kann. Dazu müsste ich mir schon Zugriff auf sein Linux-System verschaffen.

An meinem OpenWRT Router habe ich also folgendes in die zusätzlichen Firewallregeln (/etc/firewall.user) eingefügt:

# SSDP: Increase DLNA Broadcast TTL
iptables -t mangle -A PREROUTING -i br-lan -d 239.255.255.250 -j TTL --ttl-inc 1
iptables -t mangle -A PREROUTING -i br-IOT -d 239.255.255.250 -j TTL --ttl-inc 1
iptables -t mangle -A PREROUTING -i br-SRV -d 239.255.255.250 -j TTL --ttl-inc 1
ip6tables -t mangle -A PREROUTING -i br-lan -d ff02::c -j TTL --ttl-inc 1
ip6tables -t mangle -A PREROUTING -i br-IOT -d ff02::c -j TTL --ttl-inc 1
ip6tables -t mangle -A PREROUTING -i br-SRV -d ff02::c -j TTL --ttl-inc 1

Mittels --ttl-inc wird die TTL um eins erhöht, sodass die betreffenden Pakete 1x (von einem Subnetz ins nächste) geroutet werden können.

Nicht zu vergessen: Firewallfreigaben für DLNA

Eine Sache fehlt noch: Sollte sich eine restriktive Firewall zwischen den Subnetzen befinden, müssen für SSDP und alle anderen DLNA-Übertragungen noch Firewallfreigaben erteilt werden. In meinem Netzwerk muss also konkret eine eingeschränkte Kommunikation zwischen den Netzen lan, iot und srv stattfinden können.

Für SSDP soll die Firewall für Zieladresse 239.255.255.250 bzw. ff02::c und Port 1900 (UDP) durchlässig werden:

config rule
option name 'allow_iot_srv_ssdp'
option dest_port '1900'
option src 'iot'
option dest 'srv'
option target 'ACCEPT'
list proto 'udp'
list dest_ip '239.255.255.250'
list dest_ip 'FF02::C'
config rule
option name 'allow_iot_lan_ssdp'
option dest_port '1900'
option dest 'lan'
option target 'ACCEPT'
list proto 'udp'
option src 'iot'
list dest_ip '239.255.255.250'
list dest_ip 'FF02::C'
config rule
option name 'allow_srv_iot_ssdp'
option dest_port '1900'
option src 'srv'
option dest 'iot'
option target 'ACCEPT'
list proto 'udp'
list dest_ip '239.255.255.250'
list dest_ip 'FF02::C'
config rule
option name 'allow_srv_lan_ssdp'
option dest_port '1900'
option src 'srv'
option dest 'lan'
option target 'ACCEPT'
list proto 'udp'
list dest_ip '239.255.255.250'
list dest_ip 'FF02::C'

Mögliche Verbindungen sehen also so aus:

IoT => SRV
IoT => LAN
SRV => IoT
SRV => LAN

Vom LAN-Netz ausgehende Verbindungen sind hier nicht explizit aufgelistet, denn sie sind in meinem Netzwerk generell erlaubt und nicht eingeschränkt.

Die SSDP-Freigaben sind hiermit erledigt. Die Geräte könnten sich schon untereinander finden, doch noch keine Inhalte austauschen. Hier für muss eine Freigabe für den eigentlichen Stream-Port einegrichtet werden. Im Fall von meinem Jellyfin-DLNA-Server werden Inhalte via Port 8096 (TCP) gestreamed. Meine Freigaben dafür sehen so aus:

config rule
option name 'allow_iot_srv_nas_dlna_http'
option src 'iot'
option dest 'srv'
list dest_ip '192.168.3.2'
list dest_ip 'fd00:0:0:3::2'
option target 'ACCEPT'
list proto 'tcp'
option dest_port '8096'

Mehr Freigaben sind nicht nötig, denn Geräte aus dem LAN-Bereich dürfen ohnehin auf alles Zugreifen und Geräte aus dem SRV-Netz sollen niemals Streams aus dem IoT Netz oder dem LAN-Netz empfangen. Beim SSDP-Protokoll wurden bewusst mehrere Richtungen und Kombinationen berücksichtigt und freigeben, damit nicht nur SSDP NOTIFY (announcements) den richtigen Weg finden, sondern auch Fragen nach SSDP-Diensten (M-SEARCH). Generell ist hier aber nur das streamen vom SRV-Bereich entweder in LAN oder IOT vorgesehen.

Vergesst nicht, eure Änderungen an der Firewall zu aktivieren!

uci commit firewall
service firewall restart

Nach idR. weniger als 2 Minuten sollten sich die Geräte gemäß der FW-Konfiguration finden können. In meinem Beispiel der Fernseher im IoT Netz und der DLNA-Server im SRV Netz.

Die Sache mit IGMP

Je nach Konfiguration der Bridges auf dem OpenWRT-Router (bei mir br-lan, br-IOT und br-SRV) kann es vorkommen, dass sich DLNA-Geräte nicht oder nicht sofort finden. Das hängt mit dem Einsatz des IGMP-Protokolls zusammen…

Routet man Multicast-Pakete über Netzwerkgrenzen, kann es vorkommen, dass Netzsegmente von Multicast-Paketen geflutet werden, die keinen einzigen Client haben, welcher an diesen Paketen interessiert ist. Besonders dramatisch kann das Problem bei Multicast-Streams auftreten, wie z.B. IPTV, da hier entsprechend größere Mengen für den Videostream verteilt werden. Um unnötige Belastungen in größeren Netzwerken zu vermeiden, wurde IGMP (Internet Group Messaging Protocol) erfunden: Mittels IGMP kann ein Gerät an den Router melden, ob und an welchen Multicast-Paketen es interessiert ist. Erkennt der Router an einem Interface ein Gerät, das Interessen an Multicast-Paketen bekundet, leitet dieser die entsprechenden Pakete an dieses Subnetz weiter. Andernfalls nicht.

Normalerweise sollte IGMP auf dem OpenWRT Router für alle Bridges aktiviert sein. Das bedeutet: Wollen Geräte SSDP Pakete für DLNA empfangen, müssen sie diese beim Router zunächst “abonnieren”. Das funktioniert je nach Gerät mal mehr, mal weniger gut. Wer mit seinem Setup auf Probleme stößt oder gar keine SSDP Pakete in seinem Netzwerk erkennen kann, sollte einmal versuchen, IGMP auf allen involvierten Bridges am Router zu deaktivieren. Der Traffic erreicht dann immer alle Bridges (und somit Subnetze).

Wer in seinem Netzwerk ohnehin keine Multicast-Videostreams verteilt, kann IGMP getrost deaktiviert lassen. SSDP verursacht nur minimalen Traffic, der nicht ins Gewicht fällt. Wer hingegen IPTV im Netzwerk laufen hat und mehrere Subnetze betreibt, will IGMP sehr wahrscheinlich aktiviert lassen.


Weiterführende Links:


Apple Airplay über Subnetze hinweg ermöglichen (OpenWRT)

$
0
0

Ähnlich wie DLNA ist auch Apple Airplay nicht unbedingt für den Betrieb außerhalb eines einzigen Subnets ausgelegt. Mit ein paar Kniffen funktioniert es trotzdem …

mDNS: Basis vieler Heimnetzwerk-Dienste

Zunächst ist wichtig zu verstehen, dass Apple Airplay auf einer Sammlung von Standards und Protokollen aufbaut. Es genügt nicht den einen “Airplay-Port” in der Firewall durchzureichen und zu hoffen, dass alles funktioniert. Damit sich Airplay-Geräte in einem Netzwerk überhaupt gegenseitig finden können, wird das Multicast-DNS (auch mDNS) zur sog. Service Discovery eingesetzt. Dabei handelt es sich um nichts anderes als einen lokalen, dezentralen Verzeichnisdienst. Anders als beim herkömmlichen DNS gibt es also keine zentrale Serverinstanz, die sämtliche verfügbare Services und die zugehörigen Adressen vorhält. Durch regelmäßiges Bekanntgeben der selbst angebotenen Dienste via Broadcast / Multicast werden alle anderen Netzteilnehmer darüber in Kenntnis gesetzt, dass ein Service eines gewissen Typs unter einer bestimmten Adresse verfügbar ist.

Über das avahi-browse Tool können zum Beispiel folgende Einträge im eigenen Netzsegment abgerufen werden:

[thomas@thomas-nb]~% avahi-browse -a
+ wlp58s0 IPv6 [LG] webOS TV UK6470PLC _airplay._tcp local
+ wlp58s0 IPv4 [LG] webOS TV UK6470PLC _airplay._tcp local
+ wlp58s0 IPv6 LG webOS TV 8A66 _hap._tcp local
+ wlp58s0 IPv4 LG webOS TV 8A66 _hap._tcp local
+ wlp58s0 IPv6 Philips Hue - <id> _hue._tcp local
+ wlp58s0 IPv4 Philips Hue - <id> _hue._tcp local
+ wlp58s0 IPv4 turris _ssh._tcp local
+ wlp58s0 IPv4 Chromecast-Ultra-<id> _googlecast._tcp local
+ wlp58s0 IPv6 turris-2 _http._tcp local
+ wlp58s0 IPv6 EPSON ET-2650 Series-<id> _http._tcp local
+ wlp58s0 IPv6 <id> _teamviewer._tcp local
+ wlp58s0 IPv6 EPSON ET-2650 Series-<id> _printer._tcp local
+ wlp58s0 IPv6 EPSON ET-2650 Series-<id> _ipps._tcp local
+ wlp58s0 IPv4 RX-V777 <id> _http._tcp local
+ wlp58s0 IPv4 eDMP32MB_<id> _spotify-connect._tcp local
+ wlp58s0 IPv4 <id>@RX-V777 <id> _raop._tcp local
+ wlp58s0 IPv4 <id> _fosquitto._tcp local
[...]

Wie deutlich zu sehen ist, senden viele Geräte im Heimnetzwerk solche mDNS-Pakete und geben darüber Informationen über ihre Services preis. So informiert mein Drucker zum Beispiel darüber, dass er via IPP angesprochen werden kann. Ein Desktoprechner bewirbt seinen verfügbaren Teamviewer-Server und meine Philips Hue Bridge lässt andere Geräte (z.B. mein Smartphone) wissen, dass sie über ein Philips-spezifisches “hue” Protokoll angesprochen werden kann.

Geräte, die sich nicht im selben Netzsegment befinden, werden an dieser Stelle nicht sichtbar, weil mDNS (wie auch SSDP) standardmäßig nur im selben Subnetz funktioniert. Mein Fernseher und AV-Receiver, die beide Airplay beherrschen, bleiben also verborgen.

In meinem Artikel “DLNA und andere SSDP Dienste über lokale Subnetze hinweg nutzen (OpenWRT)” habe ich bereits einen Weg aufgezeigt, wie sich Multicast-Protokolle auch über verschiedene Subnetze hinweg nutzen lassen. Prinzipiell wäre es auch möglich, dasselbe für mDNS umzusetzen. Statt des SSDP-Multicast-Bereichs müsste dann der mDNS-Adressbereich im Router konfiguriert werden.

Für Airplay bzw. mDNS habe ich aber etwas anderes ausprobiert: Einen Avahi-Reflector!

Avahi (-Reflektor)

Auf meinem Router - einem Turris Omnia 2020 mit OpenWRT - befindet sich bereits der Avahi-Daemon. Avahi ist ein Daemon, der via mDNS im lokalen Netz verkündet, welche Services ein Gerät anbietet. Auf meinem Omnia kündigt Avahi beispielsweise SSH auf Port 22 und die Weboberfläche auf Port 80 an.

Praktischerweise kann Avahi mDNS-Einträge von einem Subnetz / Interface auslesen und dann in ein anderes Subnetz replizieren (oder auch “reflektieren”). Korrekt konfiguriert werden angekündigte Services aus Netz A also auch im Netz B und C angekündigt. Die dafür notwendige Konfiguration sieht in meinem Fall so aus:

(/etc/avahi/avahi-daemon.conf)

[server]
host-name=turris
domain-name=local
use-ipv4=yes
use-ipv6=yes
check-response-ttl=no
use-iff-running=no
allow-interfaces=br-lan,br-SRV,br-IOT
[publish]
publish-addresses=yes
publish-hinfo=yes
publish-workstation=no
publish-domain=yes
publish-aaaa-on-ipv4=no
publish-a-on-ipv6=no
[reflector]
enable-reflector=yes
reflect-ipv=no
[rlimits]
rlimit-core=0
rlimit-data=4194304
rlimit-fsize=0
rlimit-nofile=30
rlimit-stack=4194304
rlimit-nproc=3

Bedeutend sind vor allem die Zeilen unter [reflector] und allow-interfaces. Hinter den Interfaces befinden sich Bridged für meine Subnetze für LAN, Server und IoT Geräte. enable-reflector=yes sorgt schließlich für das Spiegeln der Einträge zwischen den Netzen.

Nach einem Serviceneustart sollte das Airplay-Gerät auf den anderen Clients zumindest schon einmal über das avahi-browse Tool sichtbar sein.

Firewall-Konfiguration

Die Firewall zwischen meinen Subnetzen ist relativ restriktiv, sodass meine Arbeit hier noch nicht erledigt ist. Airplay benötigt noch einige Freischaltungen, damit es korrekt funktioniert.

In meinem Szenario sollen nur Geräte aus dem LAN (br-lan) Inhalte an meinen AV-Receiver (br-IOT) mit Airplay senden können. Folgende Konfiguration habe ich hierzu in /etc/config/firewall hinterlegt:

config rule
option dest_port '80'
list proto 'tcp'
option name 'allow_iot_yamaha_lan_ipad_airplay_http'
option dest 'lan'
option target 'ACCEPT'
option src 'iot'
config rule
option dest_port '443'
list proto 'tcp'
option name 'allow_iot_yamaha_lan_ipad_airplay_https'
option dest 'lan'
option target 'ACCEPT'
option src 'iot'
config rule
option dest_port '554'
option src 'iot'
option name 'allow_iot_yamaha_lan_ipad_airplay_rtsp'
option dest 'lan'
option target 'ACCEPT'
config rule
option dest_port '3689'
option dest 'lan'
option target 'ACCEPT'
option src 'iot'
option name 'allow_iot_yamaha_lan_ipad_airplay_daap'
list proto 'tcp'
config rule
option dest_port '49152-65535'
option src 'iot'
option name 'allow_iot_lan_airplay_dynamic'
option dest 'lan'
option target 'ACCEPT'

Die Firewallregeln lassen Geräte aus dem IoT-Netzsegment auf definierte Resosurcen aus dem LAN-Segment zugreifen, denn beim Aufbau einer Airplay-Verbindung bietet die Streamingquelle (z.B. iPad) auf einem Port die Inhalte an - das Abspielgerät greift dann darauf zu. Die Freigaben müssen also gewissermaßen “umgekehrt” betrachtet werden. Nicht der Port am Abspielgerät wird freigeschaltet, sondern der Port an der Quelle.

Wenn nur bestimmte Geräte miteinander kombinierbar sein sollen, ließen sich die Regeln durch die Angabe von “Source” und “Destination” IP-Adressen noch verschärfen. Ich habe der Einfachheit darauf verzichtet.

Eine Liste der von Airplay verwendeten Ports lässt sich übrigens hier einsehen. Insbesonders die letzte Regel hat mir Kopfschmerzen bereitet, weil sie nicht explizit im Zusammenhang mit “Airplay” aufgelistet ist.

T-Shirts für meine Mastodon-Instanz metalhead.club

$
0
0

Zwei Dinge sind in der Regel Mangelware, wenn man eine Mastodon-Instanz betreibt: Finanzielle Mittel und öffentliche Aufmerksamkeit. Doch beide sind wichtig, damit die Instanz weiter betrieben werden kann und - falls gewünscht - ein Wachstum oder Reichweite erzielen kann.

Mit metalhead.club verfoge ich das Ziel, eine professionell gehostete Plattform für alle anzubieten, die sich im Metal-Musikgenre heimisch fühlen. Damit eine solche themengebundene Instanz leben kann und ihre Nutzer den größten Nutzen davon haben, muss sie eine gewisse Bekanntheit erlangen. Dennoch kommen die üblichen Werbemittel nur bedingt in Frage - sei es, weil sie nicht meine Vorstellung von Datenschutz implementieren oder aber derzeit nicht ohne weiteres sinnvoll finanzierbar sind. Mit den kostbaren Spendengeldern muss sparsam umgegangen werden.

Zudem ist die Zielgruppe zwar groß, aber relativ speziell: Während man ein LED-Leuchtmittel vermutlich an jeden verkaufen kann, gelingt das mit einem sozialen Netzwerk für Fans des Metals nicht ganz so einfach. Die richtigen Menschen müssen in ihrer “Sprache” angesprochen werden.

… und was bietet sich da besser an, als etwas zu verkaufen, das in der Szene einen hohen Stellenwert hat und gut sichtbar ist? T-Shirts! Als Bandshirts sind sie privat, auf Festivals oder Konzerten überall zu sehen und zeigen, welche Bands der Träger gerne hört. Ähnlich soll es auch mit den Instanz-T-Shirts funktionieren: Als jemand, der ein T-Shirt seiner Instanz trägt, kann man als eine Art mobiler Werbeträger funktionieren und gleichzeitig das fördern, was man selbst unterstützenswert findet - die metalhead.club Mastodon-Instanz!

Nachdem sie von einigen metalhead.club Mitgliedern bereits seit langem enthusiastisch gefordert wurden, habe ich im Sommer 2023 die erste T-Shirt Bestellaktion ausgerufen und 100 metalhead.club T-Shirts bestellt und an die Mitglieder verkauft. Nun - im Frühjahr 2024 - war es Zeit für die zweite Aktion mit einem anderen T-Shirt-Modell.

Wie ich an das Projekt “metalhead.club T-Shirts” herangegangen bin, will ich im folgenden beschreiben und den ein oder anderen Hinweis geben. Vielleicht hilft es so manchem, der auch mit dem Gedanken spielt, Merchandise zu seiner Mastodon-Instanz anzubieten.

Tom wearing metalhead.club t-shirt

Selbst machen oder zurücklehnen?

Im Internet und in den Städten gibt es zahlreiche Shops, die verschiedenste Merch-Artikel anbieten und dabei häufig sogar den Versand zum Käufer direkt übernehmen. Beispiele dafür sind Spreadshirt, Red Bubble oder Printful. Teilweise lassen sich die Lieferanten sogar automatisch über Onlineshop Plugins beauftragen, sodass man als Verkäufer der Artikel eigentlich nicht mehr viel unternehmen muss: Der Kunde bestellt in einem Onlineshop, der Lieferant führt die Produktion aus, wickelt die Zahlung ab und verschickt die Ware an den Kunden. Als Betreiber des Shops kassiert am Ende ein paar Euro vom Verkauf.

Bei meinem T-Shirts will ich jedoch einen anderen Weg gehen. Solange es die Stückzahlen (ca 100 Stück Pro Aktion) zulassen, will ich selbst in den Bestellprozess involviert sein: Zahlungsentgegennahme und Versand führe ich selbst durch - auch wenn es zum Teil mühselige Arbeit ist. So kann ich vermeiden, dass unnötig Geld in Form von Provisionen und Gebühren an Dritte fließt und bin außerdem in der Lage, einen datenschutzfreundlichen Einkauf zu ermöglichen: Die Kunden und ihre Adressdaten kenne nur ich. Nach der Verwendung können sie gelöscht werden. Außerdem kann ich selbst die Qualität kontrollieren und die Verpackung auswählen, sowie nach belieben Extras dazugeben, beispielsweise metalhead.club Visitenkarten.

Obwohl ich zunächst mit dem Gedanken gespielt habe, die T-Shirts selbst zu bedrucken, habe ich die Idee schnell verworfen. Denn dafür sind neben dem passenden Equipment auch viel Zeit und Platz nötig - alles Dinge, die ich nicht habe. Meine T-Shirts lasse ich von einem Zulieferer einkaufen und bedrucken. Das Resultat vertreibe ich dann selbst weiter.

Die Zutaten: T-Shirts und (Sieb-)Druck

Wenn es um Merchandise oder Teambekleidung mit Druck geht, gibt es einen großen Namen in der Branche: Stanley/Stella. Das Unternehmen stellt seit 2012 hochqualitative Kleidung her, die dann von Dritten weiter gestaltet werden kann - eine Art “Blankokleidung”. Ein besonderes Merkmal ist die Biobaumwollzertifizierung.

Die T-Shirts der Marke wurden mir im Gespräch mit einigen metalhead.club Mitgliedern wärmstens empfohlen und mir die hohe Qualität bestätigt. Mittlerweile kann ich auch eine absolute Empfehlung aussprechen - die Kleidung hat sich bewährt und der Preis stimmt. Der Name war also gesetzt.

Die Modellauswahl ist Geschmackssache. Die erste Bestellaktion drehte sich um die Modelle “Presenter” und “Evoker” - bei der zweiten Aktion habe ich mich auf das “Crafter” Modell beschränkt, um zusätzlichen Aufwand zu vermeiden. Auch die Farbe habe ich auf Schwarz eingeschränkt, um nur noch die Kleidergrößen als einzige Variable zu haben. Das macht die Abwicklung einfacher und somit weniger fehleranfällig. Es empfiehlt sich, von allen Größen ein paar Sütck mehr zu bestellen. Sei es, um Qualitätsmängel auszugleichen, verlorene Sendungen zu ersetzen, oder einfach nur, um ggf. über eine größere Stückzahl einen besseren Stückpreis zu erzielen. Überschüssige T-Shirts bin ich bei der Bestellaktion 2023 relativ schnell losgeworden, da nicht alle Interessenten von der Aktion erfahren haben oder sie aus anderen Gründen verpasst haben.

Bezüglich des Druckverfahrens war für mich ziemlich schnell klar, dass ich einen Siebdruck wollte. Im Vergleich ist es zwar nicht unbedingt das günstigste oder schnellste Verfahren, aber es verspricht eine sehr hohe Haltbarkeit, insbes. bei häufigem Waschen, da die Farbe tief in das Gewebe eindringen und sich dort gut halten kann. Was wäre denn ein metalhead.club T-Shirt, das Mosphits nicht standhält und nach ein paar Wäschen nicht mehr lesbar ist?

Meine T-Shirts lasse ich von Promoyard bedrucken - einem regionalen Unternehmen, das schon bei der ersten Bestellung überzeugen konnte. Gedruckt wird mit ÖkoTex-zertifizierten, hautfreundlichen Farben.

T-Shirts in a box

Der Bestellablauf

Nachdem T-Shirt Hersteller und Druckerei feststanden, habe ich angefangen, die Aktion zu bewerben. Zum einen habe ich auf meinem 650thz.de Services Blog einen Post verfasst, der die Information zu den T-Shirts und Bestellinformationen zusammenfasst - zum anderen habe ich auch auf Mastodon Beiträge zu der Aktion verfasst und eine Admin-Ankündigung für meine Instanz metalhead.club eingestellt. So wird jedes Mitglied über eine gesondert eingeblendete Nachricht in Web und App über die T-Shirt Aktion benachrichtigt.

Nach der ersten Bestellaktion im Sommer 2023 war klar, dass ich häufiger auf die Aktion hinweisen musste, um möglichst alle Interessenten zu erreichen. Der Bestellzeitraum war zwar 6 Wochen lang, aber durch die Urlaubszeit und zeitlich nur relativ konzentrierte Hinweise hat nicht jeder von der Bestellaktion erfahren.

Beim zweiten Mal im Frühjahr 2024 habe ich zu Beginn täglich morgens einen Post abgesetzt, um Mitglieder (zumindest in Deutschland) morgens vor der Arbeit oder auf dem Weg zu erreichen, wenn sie Zeit für Mastodon haben. Offenbar konnte ich so noch einige Interessenten erreichen. Meine Hinweise habe ich innerhalb der sechswöchigen Bestellfrist zum Ende hin zwar reduziert, um niemanden zu langweilen, aber kurz vor Ablauf der Frist noch einmal vermehrt gepostet, um Kurzentschlossene abzufangen.

Technisch habe ich die Bestellungen sehr einfach abgewickelt: Wer an einem T-Shirt interessiert war, schrieb mir eine E-Mail mit Anzahl der T-Shirts, Größe und Anschrift. Im Anschluss wurde dem Besteller eine E-Mail mit einer Empfangsbestätigung und der Bankverbindung sowie einem Stripe.com Link zur Bezahlung zugeschickt. Die Zahlungen habe ich täglich manuell abgeglichen und vermerkt. Nach eingehender Zahlung wurde nochmals eine Zahlungsbestätigung via E-Mail versendet. Alle Bestellungen und den Bezahlstatus habe ich in einer unspektakulären LibreOffice Calc Tabelle festgehalten.

Spätestens nach der ersten T-Shirt Aktion schwebte mir zwar eine Zeit lang eine einfache Django-basierte Bestellverwaltungsoberfläche vor, doch ich fand nicht ausreichend Zeit, diese zu entwickeln. Für Bestellungen in der Größenordnung um 100 Stück war aber auch eine einfache Tabellenkalkulationsanwendung noch in Ordnung - wenn auch weniger praktisch.

Eine Bezahlung via Vorkasse habe ich übrigens gewählt, um das Risiko auf meiner Seite gering zu halten, etwa durch verspätete oder ausbleibende Zahlungen. Glücklicherweise haben sich die Mitglieder auf metalhead.club (und auch Besteller anderer Instanzen) aber als aüßerst pflichtbewusst und zuverlässig erwiesen. Probleme mit der Bezahlung gab es nicht.

Pile of packages

Der Versand

Nachdem die Bestellungen gesammelt waren und der Bestellzeitraum endete, gab ich meinerseits die Herstellung der T-Shirts bei meinem Lieferanten in Auftrag. Innerhalb von ca 2 Wochen wurden die T-Shirts geliefert und ich machte mich daran, diese nach und nach an die Mitglieder zu verschicken.

Versandmaterial

Beim Versandmaterial habe ich mir viele Gedanken gemacht, denn es sollte die Ware nicht nur trocken und sicher ans Ziel bringen, sondern auch möglichst umweltfreundlich und ggf. wiederverwendbar sein. Mit Biobiene.com habe ich genau den richtigen Lieferanten dafür gefunden! Der Shop bietet verschiedene Versandmaterialien auf Graspapier-Basis und andere umweltfreundliche Alternativen an.

Zum Einsatz kamen bei der ersten metalhead.club T-Shirt Aktion folgende Materialien:

Die Kleiderschutzhüllen sind mit ihren DIN-A3 Maßen zwar etwas zu groß, lassen sich aber dafür umso komfortabler besser mit den T-Shirts füllen. Die Alternative - “Polybeutel” aus Papier sind zwar pro Stück günstiger, können allerdings erst ab 250 Stück gekauft werden. Ich hab mich also für erstere entschieden.

Die Versandtaschen aus Graspapier können nach dem Aufreißen ein zweites Mal benutzt werden: Ein zweiter, innenliegender Klebestreifen ermöglicht den mehrmaligen Einsatz.

Die Lieferscheinhüllen sind für Rechnungen gedacht, die im Falle internationaler Sendungen (nicht-EU-Ausland) für den Zoll notwendig sind. Außerdem muss auf den Versandtaschen zusätzlich eine Zolldeklaration aufgeklebt werden - mit Angaben über den Inhalt, Gewicht und Wert.

Die Empfängeradressen habe ich zunächst auf Papier ausgedruckt und aufgeklebt, allerdings stellte sich das - wenig überraschend - als sehr zeitaufwendig heraus. Später habe ich mir bedruckbare Aufkleber in DIN-A4 Größe bestellt, die Adressen darauf ausgedruckt und ausgeschnitten. Die Deutsche Post fasst bei der Online-Eingabe mehrerer Adressen alle Anschriften zu einer PDF-Seite zusammen (max 10 Anschriften pro Seite) und bietet diese zum Ausdruck an. Ausgedruckt können die Anschriften dann ausgeschnitten und direkt aufgeklebt werden.

Für die zweite Aktion wollte ich auch auf umweltfreundliche Adressaufkleber setzen und habe mir Heisap Papieretiketten HEI027 bestellt.

Versandtarife bei Post und DHL

Den Versand habe ich für Empfänger in Deutschland über die Deutsche Post abgewickelt. Über die Warensendung 500 können problemlos auch 3 T-Shirts in einer Versandtasche verschickt werden.

Sendungen ins EU-Ausland und weltweit habe ich über DHL verschickt. Das “Päckchen EU / bzw. International XS 2 kg” eignet sich dafür.

Sendungen innerhalb Deutschlands waren idR. nach 3 Werktagen bei den Empfängern. Bei internationalen Sendungen vergingen aber zum Teil mehrere Wochen. Vor allem bei Sendungen nach Kanada und Mexiko war Geduld gefragt. Aber auch Sendungen nach Spanien und Ungarn schienen zunächst verschollen zu sein, bevor sie viele Tage nach dem erwarteten Empfangsdatum noch in Postfilialen gefunden wurden. Es lohnt sich also, Geduld zu haben (und den Empfänger ggf. anzuweisen, noch einmal nachzufragen und das Päckchen suchen zu lassen).

Many packages in two Deutsche Post transport boxes

Versenden der T-Shirts

Sobald die T-Shirts und das Versandmaterial da sind, kann es losgehen. Beim letzten Mal habe ich den Versand der knapp 100 T-Shirts über ca 3 Wochen gestreckt, um nicht die Konzentration zu verlieren. Denn Fehler beim Versand können relativ schnell relativ teuer werden - vor allem, wenn es um Empfänger im Ausland geht.


Die T-Shirt-Aktion 2023 war ein voller Erfolg und die Bestellphase für die Aktion Frühling 2024 hat letzte Woche geendet. Ich bin gespannt auf die zahlreichen Fotos der metalhead.club Member mit ihren T-Shirts! Es ist schön, die Mitglieder auf den Fotos in einer Art gemeinsamen Uniform zu sehen!

T-Shirts with metalhead.club business cards on top

Minix Z100-0dB als neuer Homeserver für Backups und Medienstreaming

$
0
0

2018 habe ich ein kleines ARM-NAS auf Basis des damals relativ neuen Rock64 Boards von Pine aufgebaut. Das NAS sollte nur leichte Aufgaben wie z.B. das Speichern von Laptop-Backups und Bereitstellen von DNS-Diensten übernehmen. Die Aufgabe hat es seither prima gemeistert. In den letzten Jahren ist allerdings eine neue Aufgabe dazugekommen, die etwas mehr Leistung benötigt: Das Verwalten meiner Medienbibliothek.

Das Rock64 NAS stößt an seine Grenzen

Mittels Jellyfin-Medienserver sollte meine Musik Zuhause und unterwegs immer verfügbar sein. Auch einige von DVD gerippte Konzertfilme sollten via DLNA auf dem Fernseher verfügbar gemacht werden. Mit Musik klappte das Streaming leistungsmäßig, aber beim Thema Video ist das Rock64 Board mit seiner relativ betagten Rockchip CPU schnell an seine Grenzen gekommen. Da der LG Fernseher nur ganz bestimmte Videocodecs versteht, fordert er beim DLNA Server häufig Videotranscoding an. Dieses Transcoding ist relativ leistungshungrig, wenn nicht spezielle CPU-Hardwareeinheiten vorhanden sind. Zudem muss sichergestellt werden, dass das HW-basierte Transcoding durch die Software genutzt werden kann. Entsprechende Libraries und Treiber müssen auf dem System also installiert sein.

Wie im ARM Umfeld (leider) schon fast üblich, ist die Softwaresituation etwas unübersichtlich. Es gibt in der Regel eine schlecht gewartete Linux-Variante vom Boardhersteller und einige Community-Distributionen, die versuchen, es besser zu machen. Jede hat allerdings andere Baustellen und es ist nicht einfach, die eine Distribution zu finden, die auf dem neuesten Stand ist, stabil funktioniert und dabei alle Features unterstützt, die auf dem Board bereitstehen. Ich bin lange Zeit mit Armbian gut gefahren, allerdings konnte nie eine HW-Beschleunigung für die Videotranscodierung zum Laufen bringen.

Das Thema habe ich ehrlicherweise aber auch nicht sehr intensiv verfolgt, denn auch an anderer Stelle schwächelte mein Rock64 System. Alleine schon der Aufbau derJellyfin-Website dauerte einige Sekunden - gefolgt von weiterem Warten, bis Alben-Thumbnails meiner Musikeinkäufe geladen waren. Spaß hat das nicht gemacht und ein Durchsuchen war relativ mühselig.

Alles neu macht der Mai / Juni

Nun - ca 6 Jahre später, ist es Zeit, meinen Rock64 in die Rente zu schicken und ein neues, leistungsstärkeres NAS aufzubauen. Ich habe mich etwas in der Selfhosting Community umgesehen und bin relativ schnell auf die Intel N100 CPU gestoßen. Dabei handelt es sich gewissermaßen um ein x86-basiertes Konkurrenzprodukt, das Intel in das Rennen gegen die üblichen Raspberry Pi ARM Chips und Rockchip-Alternativen schickt. Die CPU kommt mit ca 6 Watt Verlustleistung aus und ist gleichzeitig relativ flott. Das macht sie perfekt für den Einsatz in einem Homeserver NAS, das die meiste Zeit nichts tut und nur sporadisch die Höchstleistung abruft.

Einen weiteren Vorteil bringt die CPU mit sich: Es mag in Zeiten von ARM Single Board Computern etwas uncool erscheinen, aber sie ist x86 basiert und verhält sich somit genauso wie jeder andere handelsübliche PC. Ich kann einfach ein eine normale Linuxdistribution darauf installieren, bin nicht abhängig von schlecht gewarteten und veralteten Linux-Kerneln, die für das jeweilige Board angepasst werden mussten und habe die volle Softwarepalette zur freien Auswahl. Zugegeben - die Situation mit Linux auf ARM ist in den letzten Jahren schon viel besser geworden. Dennoch: Es gibt immer wieder kleine Stopper, die einem die Freude nehmen.

Ein Beispiel: ZFS für Linux beherrscht Verschlüsselung. Allerdings kann diese unter ARM noch nicht auf eine Hardwareunterstützung zurückgreifen. ARM Prozessoren haben zwar eine Einheit zur Crypto-Beschleunigung, aber diese ist unter ARM noch nicht für ZFS implementiert. Auf der x86 Seite sieht es schon ganz anders aus: Hier ist die HW-unterstützte Verschlüsselung für ZFS verfügbar und lange erprobt. Auf meinem Rock64 habe ich dann schließlich auf die SW-basierte Verschlüsselung zurückgegriffen - unter Akzeptanz gewaltiger Performance-Einbußen.

Um ein weiteres Beispiel zu nennen: Auch die Acustic Analysis Funktion des Plex Media Servers ist unter ARM nicht verfügbar.

Es sollte also ein NAS mit Intel N100 werden. Da die CPU auch für Mini PCs sehr beliebt zu sein scheint, habe ich mich zuerst bei den Mini PCs nach einem passenden Modell umgesehen. Die Auswahl an asiatischen Produkten ist überwältigend. Nur wenige haben einen soliden Eindruck auf mich gemacht. Darunter auch die Produkte von Minisforum. Speziell habe ich mir den Minisforum UN100L herausgepickt. Leider scheint es kaum Mini PCs mit Anschlussmöglichkeiten für zwei SATA SSDs zu geben (geschweige denn zwei M.2 SSDs). Schnell habe ich mich damit abgefunden, weiterhin mein externes USB RAID Gehäuse verwenden zu müssen.

Während meiner weiteren Recherche habe ich außerdem bei CNX Software vom UP 7000 Edge Series gelesen. Das Gerät hat ungefähr Raspi-Formfaktor und kommt mit einem großen Kühlkörper. Der dazugehörige CNX Software Review fällt gut aus. Ich war bereits im Bestellprozess und hatte meine Kreditkarte gezückt, als ich doch noch einen Rückzieher gemacht habe. Der Preis auf der Website geht für mich zwar noch in Ordnung, allerdings ist folgendes nicht im Preis enthalten:

  • Steuern
  • Zollgebühren
  • Versand
  • Ein passendes Netzteil

All diese Punkte treiben den Preis dann schließlich auf ca 380 €. Für meinen Geschmack zu viel für das gebotene. Vor allem in Anbetracht dessen, dass Mini PCs mit SSD, min 8 GB RAM und Intel N100 schon für knapp über 200 € angeboten werden. Das Up 7000 hat mich vor allem durch seine Kompaktheit überzeugt und das “Industrial PC”-mäßige, von dem ich mir eine hohe Zuverlässigkeit erhofft habe.

Schließlich bin ich irgendwo in der Mitte zwischen Mini PC und Industrial PC hängen geblieben - nämlich bei dem Minix Z100 0dB PC. Dieser ist ebenfalls durch große Kühlrippen auf der Oberseite passiv gekühlt, überzeugt durch sein Alu-Chassis und die angebotenen Schnittstellen. Für das Betriebssystem und Anwendungen kann ich die mitgelieferte M.2 SSD nutzen. Die Nutzdaten kommen wie gehabt auf die beiden SSDs in meinem externen RAID-Gehäuse.

Foto zeigt Minix Z100-0dB in meiner Hand

Erste Eindrücke vom Minix Z100 0dB PC

Wenige Tage nach meiner Bestellung war der Minix Computer auch schon da. Selbstverständlich wollte ich das vorinstallierte Windows 11 möglichst schnell loswerden und habe - wie auf fast allen meinen Server - ein klassisches Debian in der aktuellen Version Bookworm installiert. Allerdings durfte Windows dann doch einige Male starten, denn es war gar nicht so einfach, Debian vom USB Stick zu starten. Vielleicht habe ich mich auch nur ungeschickt angestellt…

So hat es dann geklappt:

  • Außen liegenden HDMI verwenden (nicht sicher, ob es eine Rolle spielt)
  • Auf Tastatur F11 gedrückt halten
  • Einschalten

Der Boot Manager sollte sich dann öffnen. Vor der Debian Installation bin ich noch ein paar BIOS-Einstellungen durchgegangen (Menüpunkt “Enter Setup” wählen). Robtech erwähnt in seinem Video über den Minix Z100-0db, dass C states per default nicht aktiv seien und das Gerät deshalb nicht so viel Leistung hätte, wie es eigentlich könnte. Bei mir war die C-States Einstellung allerdings schon auf “enabled” gesetzt. Das deckt sich mit einem späteren Update in den YouTube Kommentaren.

Ansonsten habe ich noch eine Timeout-Einstellung auf 2 Sekunden gestellt und die Einstellung “Reset if no Monitor detected” deaktiviert, denn schließlich soll mein Minix Z100 immer ohne Monitor betrieben werden können.

Nach einem Neustart (und Abstecken des PCs) habe ich den Bootmanager noch ein zweites Mal gestartet und schließlich Debian ohne Zwischenfälle installiert.

Das Gerät macht einen sehr hochwertigen Eindruck. Wahrscheinlich trägt vor allem das massiv wirkende Metallgehäuse mit Kühlrippen auf der Oberseite dazu bei. Aber auch was die Stabilität und Funktion angeht, gibt es bisher nichts auszusetzen - ganz im Gegenteil: Ich bin begeistert von der deutlich höheren Prozessorleistung. Beeindruckend, was man einer 6-Watt CPU entlocken kann!

Wenn ich auf hohem Niveau meckern darf: Nur die Position der USB 3.2-Anschlüsse ist für meinen Einsatzzweck ziemlich ungünstig. Im Einsatz als normaler Mini PC mag es sinnvoll sein, die beiden USB 3.2 Anschlüsse an die Vorderseite zu packen. Für mich ist das eher nachteilig, weil Stromversorgung und Ethernet auf der Rückseite sowieso gebraucht werden und ich hier auch gerne mein USB 3.2 Kabel für mein SSD-RAID angesteckt hätte. So bleibt die Vorderseite aufgeräumt und ich muss das USB-Kabel nicht am Gehäuse entlang nach vorne ziehen. Auch auf die seitlich abstehenden WLAN-Antennenbuchsen könnte ich verzichten. Das ist aber nicht weiter schlimm und wohl dem geschuldet, dass das Gerät typischerweise anders verwendet werden dürfte.

Benchmarks

Ich habe mich eine ganze Weile damit beschäftigt, wie ich die Leistung meines neuen NAS bzw. Medienservers hier am aussagekräftigsten messen und beschreiben kann. Bisher bin ich noch nicht auf einen grünen Zweig gekommen spare diesen Bereich explizit aus, bis ich mit meiner Testmethodik zufrieden bin.

Lasst mich nur so viel sagen: Die Leistung ist für meine Zwecke mehr als ausreichend und es macht - anders als beim Rock64-basierten Vorgänger - Spaß, das Gerät einzurichten. Wartezeiten sind kurz und Installationsroutinen zügig. Die SSD-Controller sind nun auch mit USB 3.1 Gen 2 statt USB 3.1. Gen 1 angebunden. Damit also brutto mit 10 GBit/s statt 5 GBit/s. Und die integrierte NVMe SSD (statt einer Micro SD-Karte) für das OS bringt selbstverständlich auch nochmal einen gewaltigen Performancevorteil gegenüber dem alten Setup.

Sonstiges

Der Minix Z100 wird an der Oberseite relativ warm; deutlich über Handwarm. Aber nicht heiß. Zumindest habe ich ihn bisher noch nicht so gequält. Dass die Kühlrippen warm werden ist aber zu erwarten und beweist letztendlich ja auch nur, dass der Kühlkörper wie vorgesehen funktioniert.

Meinen NAS-Umzug habe ich auch gleich als Gelegenheit genutzt, meine Daten umzuorganisieren. Wie in diesem Beitrag erwähnt, habe ich erst vor ca 1 1/2 Jahren von einem ext4 Dateisystem auf ZFS mit Verschlüsselung umgestellt. Nun wollte ich einmal BRTFS im RAID-1 ausprobieren. Da BTRFS noch (?) keine native Verschlüsselung unterstützt, nutze ich darunter einen LUKS-Layer, um meine Daten nicht im Klartext auf die SSDs zu schreiben. Das Setup läuft bisher gut und trotz der Verschlüsselung schnell. Ich habe nichts auszusetzen. Zu BTRFS gibt es ja immer wieder einmal sehr kritische Stimmen und auch solche, die wohl schon herbe Datenverluste erlitten haben. RAID-1 scheint allerdings zu den sehr stabilen Features von BTRFS zu gehören, sodass ich mir keine Sorgen darum mache. Wir werden sehen, ob ich das Experiment bereuen werde … ;-)

Auch an der Software habe ich eine Änderung vorgenommen: Da das Musikplayback mit Jellyfin unter Android in letzter Zeit häufig Probleme bereitet hat, habe ich mir den Plex Mediaserver installiert und versuche es nun einmal damit. Erste Versuche damit sind sehr gut verlaufen und ich kann mit dem neuen NAS sogar problemlos FullHD Filme live transcodieren. Sogar 4k Material sollte möglich sein, da der Prozessor Intel QuickSync in einer modernen Version unterstützt und damit zahlreiche Videocodecs sehr effizient in HW transcodieren kann.

MakeMKV Flatpak aktualisieren

$
0
0

Wer schon einmal persönliche Sicherheitskopien von DVDs unter Linux angelegt hat, ist sicherlich mit dem Ripping-Tool MakeMKV vertraut. Das Tool kann in seiner Betaversion kostenlos genutzt werden. Der Nachteil: Dazu muss es monatlich neu aus dem Quellcode kompiliert und installiert werden.

Praktischerweise hat jemand ein FlatPak-Paket für MakeMKV angelegt, das es ermöglichst, das Programm sehr bequem auf dem FlatPak Store zu laden, ohne es selbst händisch kompilieren zu müssen. Doch leider wird das FlatPak Paket nicht automatisch aktualisiert. So kann es - wie in meinem Fall - vorkommen, dass das Programm nicht startet, weil die im FlatPak enthaltene Version schon älter als einen Monat ist und deshalb den Start verweigert.

Ich habe deshalb die FlatPak-Metadaten auf die neueste Version angepasst und bin so wieder zu einem funktionierenden MakeMKV-FlatPak gekommen. Den Prozess will ich hier kurz beschreiben:

  1. MakeMKV FlatPak Quellcode herunterladen
git clone https://github.com/flathub/com.makemkv.MakeMKV.git
cd com.makemkv.MakeMKV
  1. FlatPak Builder und Abhängigkeiten installieren
sudo dnf install flatpak-builder
flatpak install org.kde.Sdk/x86_64/5.15-23.08
flatpak install runtime/org.freedesktop.Sdk.Extension.openjdk8/x86_64/23.08

Die beiden hier erwähnten Abhängigkeiten KDE SKD und OpenJDK wurden mir ursprünglich nach dem späteren flatpak-builder Kommando als fehlende Abhängkeiten angezeigt.

  1. Buildverzeichnis anlegen
mkdir build
  1. In com.makemkv.MakeMKV.yaml Programmversionen und sha256-Hashsummen anpassen

Zum Beispiel:

 - name: makemkv-oss
[...]
sources:
- type: archive
url: https://www.makemkv.com/download/makemkv-oss-1.17.7.tar.gz
sha256: 762e552d46f9ec75a7c62dcb7d97c0fd9e6a15120d0ef6f5a080cee291d3a0ef
[...]

Im makemkv-bin Block ebenfalls:

 - name: makemkv-bin
[...]
sources:
- type: archive
url: https://www.makemkv.com/download/makemkv-bin-1.17.7.tar.gz
sha256: 8c5bc831bc952b1f873cc8450c64e392db0b2479b626d180f0ffc965668951d0
[...]
  • Gebt auf die YAML-typischen, 2 Leerzeichen breiten Einrückungen acht!
  • Bei mir war Version 1.17.7 aktuell. Prüft die aktuelle Version auf der MakeMKV Website!
  • Die SHA256 Hashsumme lässt sich mit dem sha256 Kommando und einem manuellen Download der erwähnten tar.gz Datei ermitteln.
  1. FlatPak bauen
flatpak-builder build com.makemkv.MakeMKV.yaml
  1. FlatPak installieren
flatpak-builder --user --install --force-clean build com.makemkv.MakeMKV.yaml
  1. … und starten
flatpak run com.makemkv.MakeMKV

Für die aktuelle Version habe ich hier einen Patch eingereicht: https://github.com/flathub/com.makemkv.MakeMKV/pull/87 Es ist allerdings nur eine Frage der Zeit, bis wieder nachgepatcht werden muss ;-)

Den statischen Hugo-basierten Blog mit Pagefind durchsuchen

$
0
0

Im Januar 2017 bin ich mit diesem Blog von Wordpress auf Hugo umgestiegen und habe damit auch die Suchfunktion verloren. Behelfsmäßig habe ich auf der 404-Fehlerseite ein DuckDuckGo Widget eingebaut, um verirrten Lesern zumindest irgendeine Art Suchfunktion an die Hand geben zu können. Besonders gefallen hat mir das aber nicht. Klar - irgendwie hat es schon funktioniert, aber so musste ich mich auch auf eine saubere Indizierung durch die DuckDuckGo Suchmaschine verlassen.

Vor zwei Wochen habe ich allerdings über die Hugo-Dokumentation eine sehr interessante Lösung für das Suchmaschinenproblem gefunden: Die Javascript-basierte Suchmaschine “Pagefind”.

Screenshot von Pagefind

Das Konzept ist ganz einfach: Nachdem eine Hugo-Site statisch generiert wurde, lässt man den Pagefind Indexer über die HTML-Dateien laufen. Dieser zieht sich alle möglichen Informationen aus dem Sourcecode und füttert damit einen eigenen Suchindex. Der Index kann dann vom Webbrowser der Leser bei Bedarf heruntergeladen werden und von einem kleinen Suchfeld-Javascript für eine Suche verwendet werden. Die Suche läuft also - wie man es von einer Static Site erwarten würde - ebenfalls clientseitig ab.

Index-Chunks für kurze Ladezeiten

Der Clou ist bei Pagefind allerdings, dass vom Suchscript nicht der gesamte Index heruntergeladen werden muss, sondern nur ein kleiner Teil / “Chunk”. Der Index ist auf mehrere Chunkdateien aufgeteilt und wird nur bruchstückhaft heruntergeladen. Vielleicht ist der Benutzer ja schon mit einem der ersten Suchtreffer zufrieden und benötigt keine weiteren Ergebnisse? So kann Pagefind sehr schnell und Datensparsam funktionieren.

Ein Beispiel: Der Gesamte Index (Deutsch und Englisch) für thomas-leister.de ist aktuell 3,4 MB groß und auf 87 Chunkdateien verteilt. Suche ich nach “Minix”, lädt mein Browser nur zwei kleine 41 kB Chunkdateien herunter, ehe die vorläufigen Suchergebnisse präsentiert werden. Fordere ich mehr Suchergebnisse an, werden weitere Indexchunks heruntergeladen. Selbstverständlich werden auch die Indexdateien vom Browser gecached. Einmal angeforderte Chunks müssen also nicht nochmal heruntergeladen werden.

Pagefind downloaden

Pagefind gibt es als NPX package oder auch als Binary zum Download. Die NPX Option war für mich wenig attraktiv, schließlich wollte ich nicht noch einen weiteren Paketmanager nutzen und schon gar keine gammelige und überkomplexe Javascript Buildumgebung. Die Entscheidung fiel also schnell für das Binary, welches übrigens Rust Quellcode entstammt. Das Binary habe ich einfach in mein Hugo Stammverzeichnis gelegt.

cd hugo/
wget https://github.com/CloudCannon/pagefind/releases/download/v1.1.0/pagefind-v1.1.0-x86_64-unknown-linux-musl.tar.gz -O pagefind.tar.gz
tar xf pagefind
rm pagefind.tar.gz

Suchseite anlegen

Als nächstes musste in meiner Hugo-Umgebung eine neue Seite für die Suche angelegt werden. Bei mir sollen es content/search.html bzw content/search.en.html sein. Der Inhalt sieht in etwa wie folgt aus:

+++
title = "Suche"
description = "Einen Beitrag auf thomas-leister.de suchen"
+++
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
<div id="search"></div>
<script>
window.addEventListener('DOMContentLoaded', (event) => {
new PagefindUI({ element: "#search", showSubResults: true });
});
</script>

Die beiden im Code erwähnten Dateien pagefind-ui.css und pagefind-ui.js werden beim Indizieren der Seite übrigens automatisch generiert. Darum müssen wir uns nicht selbst kümmern.

Danach wird der Hugo-Blog ein erstes Mal gebaut und indiziert (und die CSS- bzw. Javascript-Dateien generiert):

hugo --minify
pagefind --site public/

Die Seite kann hochgeladen werden und die Suchseite funktioniert bereits grundsätzlich. Nun noch ein paar Korrekturen und Optimierungen …

Einstellungen für den Dark Mode

Mein Blog schaltet automatisch zwischen einem normalen, hellen Modus und einem Dark Mode um. Damit der Dark Mode korrekt unterstützt wird, waren noch ein paar Korrekturen am CSS nötig. Glücklickerweise lassen sich mittels CSS Variablen sehr einfach Anpassungen an der Pagefind Eingabemaske und der Ergebnisliste durchführen:

Style Anpassung in meinem Theme main.css:

body {
[...]
/* Pagefind search box */
--pagefind-ui-scale: 0.9;
--pagefind-ui-font: "Geist";
}

Und für Dark Mode:

@media (prefers-color-scheme: dark) {
body {
[...]
/* Pagefind search box */
--pagefind-ui-text: #ffffff;
--pagefind-ui-background: #2d2d2d;
--pagefind-ui-border: #6a6a6a;
--pagefind-ui-primary: #dfdfdf;
}
[...]
}

Suchergebnisse verbessern

Mit den Suchergebnissen war ich anfangs noch nicht ganz zufrieden, wenn Pagefind indizierte nicht nur Artikel, sondern auch Hashtag-Indexseiten und ähnliches. Um die zu durchsuchenden Inhalte noch weiter einzugrenzen und die Suchmaschine nur auf Artikel loszulassen, habe ich noch das data-pagefind-body Attribut in mein Theme HTML eingebaut. Das Attribut wird in dem Tag ergänzt, welches den Artikeltext enthält (und möglichst keine anderen Elemente, wie Menüs, Footer etc):

zum Beispiel in der in der themes/<theme>/layouts/partials/article.html im main Tag:

<main data-pagefind-body> <!-- Main section -->
<article itemscope itemtype="http://schema.org/TechArticle">
<header>
<h1 class="article" itemprop="headline">Title</h1>
<div class="translations"></div>
</header>
{{ .Content }}
</article>
</main>

Ebenso in der themes/<theme>/layouts/partials/page.html

<main data-pagefind-body> <!-- Main section -->
<header>
[...]
</header>
{{ .Content }}
</main>

Außerdem kann man Pagefind helfen, das Veröffentlichungsdatum eines Artikels mithilfe des data-pagefind-sort="date" Attributs korrekt zu erkennen, z.B.:

<time itemprop="datePublished" datetime="{{ .Date.Format "2006-01-02" }}" data-pagefind-sort="date">{{ i18n "postdate" . }}</time>

Über das <html> Tag und das dahin enthaltene lang Attribut kann Pagefind die Sprache ermitteln. Das ist wichtig, wenn die Suchmaschine auch “Word-Stemming” beherrschen soll. Damit ist gemeint, dass eine Suche nach “laufen” auch “läuft” und “gelaufen” - also andere Formen von Wörtern - findet.

Die header.html des Themes sollte dazu etwa wie folgt aussehen:

<!doctype html>
<html lang="{{ .Lang }}">
<head>

Es gibt noch viele weitere Möglichkeiten, den Index zu verfeinern, Elemente auszublenden etc: https://pagefind.app/docs/indexing/ Für’s erste soll es das gewesen sein. Evtl. werde ich zu einem späteren Zeitpunkt noch einmal genauer mit Pagefind auseinandersetzen.

Probleme

… denn es gibt auch noch ein paar Baustellen und Punkte, die mir noch nicht so gut gefallen. Für einige wird sich vielleicht eine Lösung finden lassen:

  • Live-Modus von Hugo wird nicht unterstützt (hugo serve)
  • Artikelbilder werden manchmal nicht angezeigt, obwohl vorhanden. Siehe z.B. “Minix Z100-0dB” Artikel. Ursache: Grafik-URLs werden von Pagefind teilweise nicht korrekt ermittelt.
  • Suche ist sprachsensitiv und nutzt für die Ergebnisse nur die aktuelle Seitensprache. Ich würde gerne deutschsprachige und englischsprachige Artikel gleichzeitig durchsuchen
  • Artikel-Tags sollen bei der Indizierung explizit berücksichtigt werden

Ein Windows 10 USB Bootmedium unter Fedora / Linux erstellen

$
0
0

Kürzlich habe ich einen gebrauchten und betagten Acer Aspire E774 Laptop als Spende für den Computertruhe e.V. fertiggemacht. Selbstverständlich gehört dazu auch das überschreiben der Festplatten mit zufälligen Daten, sodass sich keine alten Daten mehr rekonstruieren lassen. Denn “gelöscht” ist nicht gleich “sicher gelöscht”. Zum sicheren Löschen habe ich das Tool shred in einer Fedora Live Umgebung genutzt. Mit beliebigen anderen Linux-Distributionen funktioniert das aber genauso gut.

Da ein Ubuntu auf dem Laptop nicht auf Anhieb lauffähig bzw. zumutbar war, weil das Touchpad nicht funktionierte und es beim Boot immer wieder merkwürdige Probleme gab, habe ich mich dazu entschlossen, nach meiner Löschaktion einfach wieder ein Windows 10 zu installieren. Ganz so, wie es zuletzt auf dem Laptop einwandfrei lief.

Kurzerhand habe ich also bei Microsoft ein WIndows 10 64-Bit Image heruntergeladen und wollte dieses auf meinen USB-Stick kopieren:

sudo dd if=~/Downloads/Win10.img of=/dev/sdc bs=8M

Nachdem das Image gebrannt war, habe ich den Stick am Laptop eingesteckt und über eine der F-Tasten beim Start den Bootmanager aufgerufen. Üblicherweise lässt sich das Bootmedium vor dem OS-start hier noch einmal anpassen, wenn die UEFI-Einstellung nicht schon korrekt ist. Schließlich wollte ich ja vom USB-Stick starten und nicht von einer der beiden verbauten Festplatten.

Doch: Nichts! Für den USB-Stick gab es keinen Eintrag. Ein Blick ins BIOS offenbarte, dass ein Start von einem USB-Medium zwar nicht die höchste Priorität hatte, aber selbst, nachdem ich das geändert hatte, zeigte mir das BIOS bzw. UEFI nur “No Boot Medium found”.

Na gut. Vielleicht war mein Bootmedium ja defekt. Ich versuchte es mit dem Fedora Media Writer - einem grafischen Tool, das standardmäßig in jeder Fedora-Installation enthalten ist. Über den Media Writer können Fedora Images auf USB-Sticks kopiert werden, aber auch beliebige andere Betriebssystemimages. Aber auch über den Fedora Media Writer gelangte ich zu keinem bootbaren USB-Stick: Selber Fehler. Auch das umstecken in andere USB-Slots brachte keinen Erfolg. Zuletzt versuchte ich sogar die manuelle Formatierung des USB-Sticks und ein anschließendes manuelles Kopieren der Dateien. Ich habe mich dabei nach dieser Anleitung gerichtet: “Windows 10 Bootstick erstellen”. Doch wieder kein Erfolg.

Schließlich kam ich über Umwege auf das Tool woeusb. Mir gefiel, dass es aus den Fedora Paketquellen installierbar war und - im Gegensatz zu Unetbootin und anderen Alternativen - über die Kommandozeile lief. Also auch in meiner Wayland-Umgebung (anders als Unetbootin!).

Mein Windows 10 Image habe ich also wie folgt auf meinen USB-Stick kopiert:

sudo woeusb --device ~/Downloads/Win10_22H2_German_x64v1.iso /dev/sda

… und hatte damit Erfolg!

Nun - wieso hat das funktioniert und meine vorherigen Versuche nicht? Das Log von woeusb liefert eine Erklärung:

WoeUSB v5.2.4
==============================
Info: Mounting source filesystem...
Info: Wiping all existing partition table and filesystem signatures in /dev/sda...
/dev/sda: 8 bytes were erased at offset 0x00000200 (gpt): 45 46 49 20 50 41 52 54
/dev/sda: 8 bytes were erased at offset 0x734ffffe00 (gpt): 45 46 49 20 50 41 52 54
/dev/sda: 2 bytes were erased at offset 0x000001fe (PMBR): 55 aa
/dev/sda: calling ioctl to re-read partition table: Erfolg
Info: Ensure that /dev/sda is really wiped...
Info: Creating new partition table on /dev/sda...
Info: Creating target partition...
Info: Making system realize that partition table has changed...
Info: Wait 3 seconds for block device nodes to populate...
mkfs.fat 4.2 (2021-01-31)
mkfs.fat: Warning: lowercase labels might not work properly on some systems
Info: Mounting target filesystem...
Info: Copying files from source media...
Splitting WIM: 4900 MiB of 4900 MiB (100%) written, part 2 of 26%
Finished splitting "./sources/install.wim"
Info: Installing GRUB bootloader for legacy PC booting support...
i386-pc wird für Ihre Plattform installiert.
installation beendet. Keine Fehler aufgetreten.
Info: Installing custom GRUB config for legacy PC booting...
Info: Done :)
Info: The target device should be bootable now
Info: Unmounting and removing "/tmp/woeusb-source-20240930-085645-Sunday.0twgOL"...
Info: Unmounting and removing "/tmp/woeusb-target-20240930-085645-Sunday.2DBYAG"...
Info: You may now safely detach the target device

Interessant sind hier zwei Stellen:

  • Zum einen wird das USB-Stick offenbar mit FAT formatiert (mkfs.fat) und nicht - wie in anderen Anleitungen zu lesen - mit NTFS
  • Und: install.wim wird “gesplittet”?!

Der Split der install.wim Datei erklärt, wieso es mit den anderen Methoden bei mir nicht funktioniert hat: Mit diesem Schritt wird die große Imagedatei (> 4 GB) auf kleinere Dateien aufgeteilt. Das ist nötig, weil das FAT Dateisystem nur mit Dateien bis zu einer Größe von 4 GB umgehen kann.

Und warum liefert Microsoft in seinem Image, welches ich per dd zu kopieren versucht habe, eine so große Datei aus? Weil das Microsoft Windows 10 ISO NTFS-formatiert ist. Und nicht FAT-formatiert. NTFS kann mit so großen Dateien problemlos umgehen - daher wurde mir in diversen Anleitungen auch dazu geraten, den USB-Stick nicht mit FAT, sondern mit NTFS zu formatieren.

Allerdings kommt hier eine weitere Besonderheit zum Tragen: Das Acer Laptop unterstützt offenbar keine NTFS-Bootmedien. … Wie das sein kann?

Der Laptop kam ursprünglich mit Windows 8 auf dem Markt. Damals enthielt das Windows Bootmedium noch keine Dateien, die größer als 4 GB waren. Dementsprechend kam man mit dem FAT Dateisystem aus. Folglich unterstützt der Acer E774 Laptop nur FAT im UEFI und nicht NTFS. Das erklärt auch, wieso er problemlos von allen Live-Linux USB-Sticks bootete, die ich ihm gab. Hier wird noch FAT benutzt.

Zusammengefasst: mit dem woeusb Tool hatte ich letztendlich Erfolg, weil:

  • Der Dell-Laptop nur FAT Medien unterstützt
  • Das woeusb Tool FAT nutzt
  • Und dabei aber dafür sorgt, dass keine der Dateien größer als 4 GB ist!

Der Schlüssel ist also woeusb’s “split” Funktion.

Auf neueren Laptops sollte das Kopieren des Win10 Images auch einfach via dd funktionieren. Für meinen älteren Laptop habe ich mit woeusb aber eine tolle Lösung gefunden.