Archiv der Kategorie: Linux

Alles rund um die Pinguine – auf dem Desktop und dem Server

TL-MR3020 v1

Ich hatte hier im Schrank noch einen älteren TL-MR3020 v1 herumliegen, auf dem sich ein olles Image von Freifunk Stuttgart befand. Von dort (FF) erhielt er keine Updates mehr, so dass er irgendwann ausgemustert wurde. Heute hab ich ihn reaktiviert mit einem LEDE für den Einsatz auf der Straße.

Zuerst musste das alte Freifunk Image runter.

Einen Rechner mit einer IP aus dem Netz 192.168.x.100 /16 ausstatten (ich nehme gerne so eine Netzmaske, weil ich leichter zwischen der TPL-Stockfirmware unter 192.168.0.1 und einem Freifunk-AP unter 192.168.0.1 wechseln kann)

Den MR3020 in den Wiederherstellungsmodus versetzen. Dazu beim Einstöpseln der Stromversorgung den WPS-Knopf gedrückt halten, bis der Router wild blinkt (ca. 5 Sekunden).

Dann via Telnet drauf verbinden

telnet 192.168.1.1

und sich Zugang via SSH verschaffen:

mount_root
passwd
dropbear
cd /tmp/

Vom Rechner aus das passende Image per SCP zum MR3020 übertragen

scp lede-17.01.5-ar71xx-generic-tl-mr3020-v1-squashfs-factory.bin root@192.168.1.1

und dieses dort Flashen:

sysupgrade -n lede-17.01.5-ar71xx-generic-tl-mr3020-v1-squashfs-factory.bin

Sollte die FF-Firmware neuer sein, als das zu flashende Image, hilft ein -F bei sysupgrade.

Der MR3020 braucht nun eine Zeit mit sich allein, in der die Stromversorgung nicht verloren gehen darf. Er sollte dann von sich aus neu booten und wieder unter 192.168.1.1 zu erreichen sein – jetzt allerdings auch im Browser per LuCI. Und in LuCI kann dann die restliche Konfiguration bequem vorgenommen werden.

Ich hab ihn mir so eingerichtet, dass er sich am verkabelten Interface per DHCP eine IP aus dem dann je lokalen Netz holt. Besonders brauchbar für den Außeneinsatz wird er, weil die MAC für dieses Interface händisch leicht gesetzt werden kann. Man besorgt sich vor Ort die MAC eines Rechners, stöpselt diesen aus und den MR3020 mit dessen MAC an – voila ist Netz für eigene Geräte da. Das geht in LuCI zügig:

  1. Network
  2. Interfaces
  3. Edit
  4. Advanced Settings
  5. Override MAC address
  6. Save & Apply

Literatur: MR3020, FF Reset

Nextcloud cannot modify header information

Das Problem mit zwei meiner Nextcloud-Installationen entstand nach dem Update von 16.0.5 auf 16.0.6: Nach dem Login im Browser wurden keine Dateien oder Ordner mehr angezeigt. Die Apache-Logs waren leer. Der Sync-Client funktionierte.

In den Nextcloud-Logs war das hier zu finden:

Cannot modify header information - headers already sent by (output started at /var/www/domain.tld/nextcloud/3rdparty/sabre/http/lib/Sapi.php:80) at /var/www/domain.tld/nextcloud/3rdparty/sabre/http/lib/Sapi.php#63

Diese Datei schien mir aber in Ordnung zu sein: ASCII als Codierung, keine whitespaces vor den ersten oder nach den letzten Einträgen.

Nach langem Hin und Her kam ich zu einer wenig eleganten Lösung: Zuerst wurde das Nextcloud Installationspaket erneut heruntergeladen und erneut an Ort und Stelle geschoben – inklusive sudo -u www-data php occ upgrade

Jetzt meckerte Nextcloud, dass die Datei mimetypelist.js den falschen Hash hätte – und weiterhin wurden weder Dateien noch Ordner angezeigt.

Also ersetzte ich die mimetypelist.js durch die Version aus dem git:

wget https://raw.githubusercontent.com/nextcloud/server/master/core/js/mimetypelist.js

und alles war wieder gut: kein Gemecker über falsche Hashes und alle Ordner und Dateien wurden brav angezeigt.

Bei beiden Nextcloud-Instanzen auf unterschiedlichen Servern die gleiche Lösung. Ich meine, dass ich serverspezifische Probleme somit ganz gut ausschließen kann. Whatever.

Webriot Update Notizzettel

Eine Notiz für mich bezüglich des Updates von Webriot:

Aktuelle Version https://github.com/vector-im/riot-web/releases herunter laden und auspacken:

wget https://github.com/vector-im/riot-web/releases/download/...
tar xzf riot-versionnumber.tar.gz

Benutzer und Rechte anpassen:

chown -R www-data.www-data riot-versionumber
chmod -R 750 riot-versionnumber

Die config Datei aus dem bisherigen Verzeichnis kopieren, evtl. auch vergleichen mit der frisch mitgelieferten und notwendige Anpassungen vornehmen:

cd riot-version/
cp -p /var/www/riotwebroot.tld/config.json .

In das Webverzeichnis von Riot wechseln:

cd /var/www/
mv /root/riot-versionnumber .

Den Symlink zum Webriotverzeichnis frisch setzen:

rm riotwebroot.tld
ln -s riot-versionnumber/ riotwebroot.tld

Die Weboberfläche aufrufen und prüfen, was schief ging (meistens nix).

Hintergrund ist /etc/nginx/conf.d/matrix.conf mit dem folgenden Inhalt:


server {
listen 443 ssl;
server_name sub.domain.tld1;
root /var/www/sub.domain.tld1;

ssl_certificate /etc/letsencrypt/live/sub.domain.tld1/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sub.domain.tld1/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;

location /_matrix {
proxy_pass http://127.0.0.1:8008;
proxy_set_header X-Forwarded-For $remote_addr;
}
}

server {
listen 443 ssl;
server_name riotwebroot.tld;
root /var/www/riotwebroot.tld;

ssl_certificate /etc/letsencrypt/live/riotwebroot.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/riotwebroot.tld/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;

}

Q2A mit LDAP gegen LD Server

Question 2 Answer ist eine nette PHP-MySQL-Alternative to askbot und bringt hier auch ein LDAP Auth-Plugin mit. Will man dieses nutzen, um gegen einen LD-Server zu authentifizieren, dann kann man die folgenden Einstellungen wählen, damit „es tut“:

q2a LDAP Einstellungen Teil 1

Selbstverständlich wäre hier für den Produktivbetrieb in unsicherer Umgebung LDAPs und Port 636 zu wählen.

q2a LDAP Einstellungen Teil 2

Ich bin zuerst nicht auf die Idee gekommen, hier AD auszuwählen und versucht zuerst mit OpenLDAP zum Ziel zu gelangen. Das wollte nicht klappen, so dass ich in einem Anfall von „mal gucken“ plötzlich wie oben gezeigt Erfolg hatte.

Matrix / Synapse password reset

Hat ein Benutzer auf dem eigenen Serverchen sein Passwort verbummelt und man sucht nach einer CLI Lösung für den Reset wird leider eine veraltete Anleitung für die Bearbeitung der Datenbank (sqlite) ziemlich weit oben angezeigt. Richtig ist der hier

https://github.com/matrix-org/synapse/blob/master/README.rst#password-reset

beschriebene Weg.

Zum heutigen Stand funktioniert demnach:

root@homeserver: hash_password
root@homeserver: sudo -u matrix-synapse sqlite3 /var/lib/matrix-synapse/homeserver.db

sqlite> UPDATE users SET password_hash='$32b$32$hiErstEhteInPasswordHashdrinderLangist/vf' WHERE name='@user:ident.server.domain';
sqlite> .quit

16.04 auf 18.04

Nur eine kurze Notiz zu meinen Erfahrungen beim Update von LXC oder auch KVM Maschinchen von Ubuntu 16.04 auf 18.04. Eigentlich läuft es nämlich erstaunlich rund.

  • Apache vergisst, dass PHP aktiviert war. Mit a2enmod lässt sich das leicht wieder fixen.
  • Die php.ini für PHP 7.2 muss man sich frisch einrichten. Übernommen wird da nix.
  • PHPMyAdmin funktioniert zwar noch nach dem Update, wirft aber bei der Betrachtung von Einzeltabellen mit Fehlermeldungen wie Warning in ./libraries/sql.lib.php#613
    count(): Parameter must be an array or an object that implements Countable nach dem User. Auf „Alles ignorieren“ klicken macht die Sicht wieder frei. Da sich der Fehler im Alltag für mich kaum auswirkt (ich nutze mysql meist direkt), habe ich mich noch nicht um eine Lösung gekümmert.
  • Opendkim bricht beim Update. Der Fehler mit status=78 lässt sich wohl nur korrigieren, wenn man opendkim zuerst purged und dann komplett neu installiert. Alle anderen Tipps, die ich mir ergooglete, halfen nicht weiter. Wer auch immer das erlebt, sollte nicht vergessen, seinen Key zu sichern. Meine Vermutung ist: Das könnte daran liegen, dass sich die Konfigurationsdateien zwischen den 16.04 und 18.04 Versionen zu arg unterscheiden.

nextCloud 15 mit LDAPs an LD-Server und Automount von Tausch und Home

Weil es so ein unschönes Gefummel war, dokumentiere ich hier für mich (und auch andere Benutzer von LD / SBE) die Anbindung der nextCloud per LDAPs an den LD-Server, die dafür sorgt, dass beim Login der Benutzer gleich noch deren Tausch- und Homeverzeichnisse in die nextCloud gelupft werden. Dass dann bei uns noch Collabora CODE dazukommt rundet die Sache schön ab.

Siehe zu diesem Thema auch den Vorgängerartikel.

Kurz zum allgemeinen Setup: Eine VM mit Ubuntu 18.04 LTS werkelt intern auf einem Virtualisierungshost, der mit seinen Netzwerkkarten in den jeweils für ihn wichtigen VLANs hängt. Auf diesem bridgen die VMs direkt in die VLANs rein. In Richtung Internet steht vor diesem VM-Host eine PFSense als Firewall in den jeweils relevanten Netzen.

Die VM für nextCloud etc. hat zwei virtuelle Netzwerkkarten: Eine zeigt via grauem VLAN in Richtung PFSense (damit in Richtung Internet) und trägt die öffentliche IP des Servers. Die andere Netzwerkkarte hängt als Bridge im grünen VLAN und wird vom LD-Server direkt versorgt. Über diese zweite („grüne“) Netzwerkkarte hole ich mir per LDAPs die Benutzerdatenbank und führe den SMB/CIFS-Mount der Homeverzeichnisse aus.

Netzwerkdiagramm

LDAPs Anbindung

Das Paket php-ldap muss an Bord und konfiguriert sein.

Hinweis: Den Zertifikatscheck kann man im nC LDAP Modul ausschalten für die ersten Tests – oder direkt auf der VM in /etc/ldap/ldap.conf durch den Eintrag TLS_REQCERT allow. Nicht schön, aber zum Testen eine Fehlerquelle weniger.

Die Server-IP mit vorangestelltem ldaps:// und im Feld Port 636 eintragen. Die zwei folgenden Felder können für LD-Server leer gelassen werden.

Da das automatische Auslesen der Base DN bei mir nicht funktioniert hat, musste ich diese von Hand angeben. In meinem Fall: ou=users,dc=kvfg-schule,dc=de

Beim LD-Server liegen die User in ldUserAccount.

Die Loginattribute wählt das von mir hier verwendete nC 15 dann von selbst richtig aus.

DIe passende Objektklasse ist posixGroup.

Das würde nun reichen, um die Benutzer in nC rein zu lassen und auch, um das Tauschverzeichnis automatisch einzubinden, aber nicht, um die Homeverzeichnisse der User automatisch zu mounten. Das liegt daran, dass nC aus dem LDAP die UUID nimmt, um die nC-Benutzernamen zu erstellen. Wir brauchen aber für den Automount der Homes unserer Benutzer deren uid (das ist dann gleichzeitig der Benutzername des Users). Es gilt demnach, nC zu überreden, die UUID zu ignorieren und stattdessen die uid der LDAP-Benutzer zu verwenden.

Auf der Registerkarte Expert finden wir diese Möglichkeit. Bei Internal Username Attribute muss uid eingetragen werden.

Hinweis: Im Reiter Advanced gibt es die Möglichkeit, die von nC lokal erstellten Benutzerverzeichnisse (im Datenverzeichnis von nC) mit %uid benamen zu lassen, statt mit der UUID. Das geschieht durch die Einstellungen oben nun automatisch so. Man darf die Angabe auf keinen Fall doppelt machen (also im Reiter Advanced und im Reiter Expert). Die Fehlermeldungen, die man nach einem Doppeleintrag erhält, beziehen sich auf Homeverzeichnispfade, die nicht aus dem LDAP gelesen werden können. Nicht wirklich hilfreich.

Das Debugging ist wenig witzig. Was hilft, ist hier schon ausführlich beschrieben worden, weswegen ich mir diese Ausführungen heute sparen will. Was hier und heute dazu kommt: Es lohnt der regelmäßige Blick in die Datenbank von nC (z.B. über phpmyadmin). Da dürfen bei den Benutzern keine UUIDs auftauchen (das sind kryptische Kombinationen aus Zahlen und Buchstaben), sondern ausschließlich deren uids (also deren Benutzernamen). Hat das nicht geklappt, darf man von Vorne beginnen. Es empfiehlt sich deswegen, zuerst eine Basiskonfiguration anzulegen und diese zu sichern, die dann wieder eingespielt werden kann, wenn man sich in eine blöde Ecke konfiguriert hat. Das ebenfalls sehr nervige LDAP-Caching von nC lässt sich mit einem beherzten Restart des Apachen beeinflussen.

SMB/CIFS Mount

Die Pakete libsmbclient php-smbclient php-smb und auch die cifs-utils müssen installiert und konfiguriert sein. Letzteres nicht nur zum Testen, ob der SMB-Mount überhaupt funktioniert, sondern auch, weil die anderen Pakete ohne die cifs-utils nicht rund laufen werden.

Nachdem den Benutzern von nC die Verwendung von SMB/CIFS erlaubt wurde, die Einträge wie im Bild aus dem Adminaccount heraus vornehmen. Dabei den Folder Name und die IP des SMB-Servers den eigenen Gegebenheiten anpassen.

Nicht irritieren lassen, dass die „Böbbel“ beim Admin rot bleiben. Da der nC-Admin nicht aus dem LDAP kommt, sondern ein rein lokaler nC-Benutzer ist, muss der SMB-Mount hier auf die Nase fallen.

Die Einträge Login-creditials, saved in session sorgen bei den LDAP-Benutzern aber später dafür, dass die automatischen Mounts klappen. Das $user sorgt für die Ersetzung des Namens für das Home-Share durch den Benutzernamen (die uid), der beim Login in der nC angegeben wurde. Deswegen ja auch das Gefrickel mit dem LDAP oben!

Die Benutzer müssen nun nur noch aufpassen, dass sie sich nicht mit dem Desktop-Client automatisch das gesamte Verzeichnis Tausch/Schule syncen 🙂

Wie man sich per Docker noch ein Collabora CODE auf die VM mit der nC holt, ist an vielen anderen Stellen im Netz schon ausführlich beschrieben worden. Für den Alttag würde ich dann 6GB RAM und 4 CPUs für die VM empfehlen: CODE wie auch nC ziehen zusammen ziemlich an den Ressourcen.

Eines noch: Moodle 3.6 bringt die Möglichkeit zur Anbindung an eine nextCloud mit.

Summa summarum: Wer braucht da noch Ella? DIY and federation are the key!

Zulip raus und Matrix rein

Seit August betrieb ich auf einer VM einen Zulip-Server zum Testen. Ich dachte, dass sich das Ding in der Familie evtl. ausbreiten würde, als Ergänzung zu unserem XMPP-Server. Dem war aber nicht so, was vor allem daran lag, dass die Android-App von Zulip wenig intuitiv daher kam und weil die Inhaltsverschlüsselung für Chats fehlte. Jetzt also Matrix-Synapse mit OLM als Verschlüsselung – wie einst Zulip hier im Haus auf einer VM betrieben mit ddnss als DynDNS Betreiber.

So richtig datenschutz-sauber ist Matrix bzw. die Android App Riot nicht. Auch dann nicht, wenn man die build aus FDroid verwendet. Diesbezüglich führt unser XMPP-Server weiterhin das Feld an und bleibt deswegen auch die Kommunikationszentrale der Familie. Aber was bei den ersten Tests von Matrix sofort überzeugte, ist die Zuverlässigkeit, mit der auch die dicksten Anhänge zugestellt werden.

Was ebenfalls für Matrix spricht ist die einfache Installation und Konfiguration. XMPP – selbst mit Prosody – und die gefühlt immer verwickelten Konfigurationsorgien bei den benötigten XEP-Modulen sind da schwerer in den Griff zu bekommen.

TC und EDS-Lite

Als Notiz für die, die es brauchen: Für EDS-Lite können TrueCrypt-Container (ja – auch VeraCrypt geht, aber das Schutzniveau muss ja nicht in und für jeden Fall so gesetzt werden) mit den Einstellungen oben schneller auf dem Rechner, als auf dem Tablet angelegt werden:

Encryption Algorithm: Twofish
Hash-Algorithm: SHA-512

Die Standardvorgaben von TrueCrypt scheinen für EDS-Lite nicht zu funktionieren.

WebUntis Scraper

Der Stundeplanrechner wirft die WebUntis HTML Dateien in ein WebDAVs Share. Dieses ist auf dem Moodle Server per Symlink in das Arbeitsverzeichnis des folgenden untisparser Skriptes eingebunden. Von dort wird das Ergebnis in ein File Repository des Lehrermoodles geschrieben und aus dem Kursraum „Schwarzes Brett“ (die Kommunikationsplattform der Schule) verlinkt. Das stellt sicher, dass nur Menschen, die a) am Moodle angemeldet und b) Mitglied des entsprechenden Kursraumes sind den Inhalt (hier: Vertretungsplan) einsehen können.

Mit geschlossenem Accordion

Accordion offen

Das muss leider so umständlich sein, weil ich keinen Weg gefunden habe, den WebDavs Ordner als solchen navigierbar im Kursraum so einzubinden, dass man diesen nicht auch von außerhalb des Moodles aufrufen könnte. Mir bleibt (sofern ich richtig liege) nix anderes über, als aus den X html Dateien, die WebUntis mir hinwirft, eine einzige zu bauen, die ich klar benamen und dann „verlinken“ kann. Dazu muss ich die vielen WebUntis HTML Dateien in ihre Bestandteile zerlegen. Ich brauche: Die Tabellen und die Zeitangaben.

Im Prinzip geht das mit beautifulsoup und Python. Meine Python-Kenntnisse sind jedoch leider noch rudimentärer als meine Bash-Kenntnisse … also muss  ein bash Skript her. Und HTML mit Bash nativ parsen  – nun: ich weiß, dass das Probleme macht. Das will man nicht.

Die Lösung ist pup, das ich in ein Bash-Skript einbinde.

Zur Information die Struktur der WebUntis Dateien in ihren Ordnern / Unterordnern:

Die zu erzeugende Output-Datei soll HTML sein und braucht dazu einen Kopf:

<html>
<head>
<title>Vertretungsplan</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0">
<meta http-equiv="pragma" content="no-cache" />

<meta http-equiv="refresh" content="120" />
<style type="text/css">
body { margin-top: 20px; margin-left: 20px; margin-right: 20px;
background: #fff; color: #272727; font: 80% Arial, Helvetica, sans-serif; }
h1 { color: #ee7f00; font-size: 200%; font-weight: bold;}
h2 { color: #ee7f00; font-size: 175%;}
h1, h2 { margin: 0; padding: 25px 0px 5px 0px;}

 /* put your css here or copy from untishtml */
 
 /* Style the buttons that are used to open and close the accordion panel */
.accordion {
    background-color: #eee;
    color: #444;
    cursor: pointer;
    padding: 18px;
    width: 100%;
    text-align: left;
    border: none;
    outline: none;
    transition: 0.4s;
}

/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active, .accordion:hover {
    background-color: #ccc;
}

/* Style the accordion panel. Note: hidden by default */
.panel {
    padding: 0 18px;
    background-color: white;
    display: none;
    overflow: hidden;
} 


</style>
</head>
<body>

<script>
  window.onload=function(){
    var acc = document.getElementsByClassName("accordion");
    for (var i = 0; i < acc.length; i++) {
      acc[i].addEventListener("click", function() {
          this.classList.toggle("active");
          var panel = this.nextElementSibling;
          if (panel.style.display === "block") {
              panel.style.display = "none";
          } else {
              panel.style.display = "block";
          }
      });
    }
  };
</script>

<h1>KvFG Vertretungsplan nach Datum und Anzeigetafel</h1>
<p><strong>Es gilt der Plan auf den Anzeigetafeln im Haus bzw. auf den Aush&auml;ngen. Diese Datei ist immer nur Beta!</strong></p>

Siehe zum Code für das Accordion im Kopf und im „Kleister“: https://www.w3schools.com/howto/howto_js_accordion.asp  Leider hat das aber nicht gereicht. Zuerst muss gewartet werden, bis die gesamte Seite geladen ist, dann erst darf die for-Schleife beginnen. Also wird das Skript in den Footer gelegt oder gekapselt in:

window.onload=function(){
... }
/* thanx Janis for debugging */

Was einen Kopf hat, braucht auch einen Fuß:

<p></p>
<p>----</p>
<p>(C) dowel 2018 | UntisParser Version 0.1</p>
<p>Fehler sind immer m&oumlglich und sollten <a href="https://yourschoolsbugtracker.tld" target="_blank">im Bugtracker gemeldet werden</a>, damit diese behoben werden k&ouml;nnen. Wer keinen Bugreport (= Bericht) formulieren kann, muss schweigen.</p>
</body>
</html>

Und dann braucht es den „Kleister“, der die Arbeit macht und alles zusammenpackt. Nennen wir es untisparser.sh und legen es in das passende Verzeichnis auf dem Server:

#!/bin/bash
# UntisParser
# 
# Parse HTML Export Files created by WebUntis
# do "e pluribus unum" with a Bash script
# throw the output into a Moodle file repository
# and link the output from inside a Moodle Courseroom
# 
# You need the pup executable from https://github.com/ericchiang/pup
# 
# (C) dowel
# License: CC BY SA https://creativecommons.org/licenses/by-sa/4.0/deed.de
# Date: 2018-08-23
# Version 0.2
# ####

# def of some base vars
RUNT=$(date '+%Y-%m-%d %H:%M:%S')
RUNF="20 Minuten" # set this in cronjob
RELOAD="120 Sekunden" # see head_filename
WORKDIR="/path/untisparser"
PUPPATH="/path/bin"
HEAD_FILENAME="kopf.html" # asumed to be in workdir
FOOT_FILENAME="fuss.html" # asumed to be in workdir
OUTPUT_PATH="/path/moodledata/repository/anzeigebretter" # set this to your Moodle file repository
OUTPUT_FILENAME="output.html"

# headline def
LUL1H="Anzeige Lehrer/innen Teil 1 Linke Seite (Heute?)"
LUL2H="Anzeige Lehrer/innen Teil 2 Rechte Seite (Morgen?)"
SUS1H="Anzeige Sch&uuml;ler/innen Teil 1 Linke Seite (Heute?)"
SUS2H="Anzeige Sch&uuml;ler/innen Teil 2 Rechte Seite (Morgen?)"


# path stuff
# Where are the Untis files in the workdir?
L_SUBDIR="$WORKDIR/Lehrerbrett"
S_SUBDIR="$WORKDIR/Schuelerbrett"

# How many of the suckers are there?
COUNT_LF1=$(/usr/bin/find $L_SUBDIR/f1 -maxdepth 1 -name '*.htm' | /usr/bin/wc -l)
COUNT_LF2=$(/usr/bin/find $L_SUBDIR/f2 -maxdepth 1 -name '*.htm' | /usr/bin/wc -l)
COUNT_SF1=$(/usr/bin/find $S_SUBDIR/f1 -maxdepth 1 -name '*.htm' | /usr/bin/wc -l)
COUNT_SF2=$(/usr/bin/find $S_SUBDIR/f2 -maxdepth 1 -name '*.htm' | /usr/bin/wc -l)

# Warning jabber because we do not know how many old files there are
# and if cleaner.script has worked the way it should have
# we asume that it failed
WARNMESS="<p>
    --------------------------------------------------------------------------------------<br>
    Stand: $RUNT | Beachte: Das Skript l&auml;uft nur alle $RUNF und die Seite wird nach $RELOAD neu geladen!<br>
    Achte auf das Datum. Es kann sein, dass ab hier alte Dateien ausgelesen werden. Wenn das so ist, dann den Rest ignorieren.</br>
    <a href=\"#top\">Ganz nach oben</a> || LuL: <a href=\"#lul1\">Links</a> | <a href=\"#lul2\">Rechts</a> || SuS <a href=\"#sus1\">Links</a> | <a href=\"#sus2\">Rechts</a>
    </p>"

# create empty output file and fill it with html head and some simple navigation stuff
/bin/cat $WORKDIR/$HEAD_FILENAME > $OUTPUT_PATH/$OUTPUT_FILENAME
/bin/echo "<id=\"top\">" >> $OUTPUT_PATH/$OUTPUT_FILENAME
/bin/echo "<p><em>Links</em> ist meist <em>Heute</em> und <em>Rechts</em> ist meist <em>Morgen</em> - aber nicht immer.</p>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
/bin/echo "<p>Anzeige Lehrer/innen: <a href=\"#lul1\">Links</a> | <a href=\"#lul2\">Rechts</a></p>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
/bin/echo "<p>Anzeige Sch&uuml;ler/innen: <a href=\"#sus1\">Links</a> | <a href=\"#sus2\">Rechts</a></p>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
/bin/echo "<p>Stand: $RUNT | Beachte: Das Skript l&auml;uft nur alle $RUNF!</p>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
/bin/echo "<p></p>">> $OUTPUT_PATH/$OUTPUT_FILENAME

# ticker export
if [ -f $L_SUBDIR/ticker.htm ]
then
    /bin/echo "<h4>L Ticker</h4>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    $PUPPATH/pup --charset iso-8859-1 -f $L_SUBDIR/ticker.htm 'marquee text{}' >> $OUTPUT_PATH/$OUTPUT_FILENAME
fi

if [ -f $S_SUBDIR/ticker.htm ]
then
    /bin/echo "<h4>S Ticker</h4>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    $PUPPATH/pup --charset iso-8859-1 -f $S_SUBDIR/ticker.htm 'marquee text{}' >> $OUTPUT_PATH/$OUTPUT_FILENAME
fi

# Create teacher part of output file

/bin/echo "<h2 id=\"lul1\">$LUL1H</h2>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
COUNT=1
MAXCOUNT=1
let MAXCOUNT=COUNT_LF1+1

# teacher links
while [ $COUNT -lt $MAXCOUNT ] ; do
    # padding
    NUM=$(printf %03d $COUNT)
    # Date extract to button
    /bin/echo "<button class=\"accordion\">" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    $PUPPATH/pup --charset iso-8859-1 -f $L_SUBDIR/f1/subst_$NUM.htm 'div.mon_title' >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "</button>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "<div class=\"panel\">" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Headline is set to H4
    /bin/echo "<h4>Lehrer/innen</h4>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "<p></p>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Table extraction
    $PUPPATH/pup --charset iso-8859-1 -f $L_SUBDIR/f1/subst_$NUM.htm 'table' >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "</div>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Warning 
    /bin/echo $WARNMESS >> $OUTPUT_PATH/$OUTPUT_FILENAME
    let COUNT=COUNT+1
done

# Reset for second part of LuL 

/bin/echo "<h2 id=\"lul2\">$LUL2H</h2>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
COUNT=1
MAXCOUNT=1
let MAXCOUNT=COUNT_LF2+1

# teacher rechts
while [ $COUNT -lt $MAXCOUNT ] ; do
    # padding
    NUM=$(printf %03d $COUNT)
    # Date extract to button
    /bin/echo "<button class=\"accordion\">" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    $PUPPATH/pup --charset iso-8859-1 -f $L_SUBDIR/f2/subst_$NUM.htm 'div.mon_title' >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "</button>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "<div class=\"panel\">" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Headline is set to H4
    /bin/echo "<h4>Lehrer/innen</h4>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "<p></p>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Table extraction
    $PUPPATH/pup --charset iso-8859-1 -f $L_SUBDIR/f2/subst_$NUM.htm 'table' >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "</div>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Warning
    /bin/echo $WARNMESS >> $OUTPUT_PATH/$OUTPUT_FILENAME
    let COUNT=COUNT+1
done

# Create pupil part of output file
/bin/echo "<h2 id=\"sus1\">$SUS1H</H2>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
COUNT=1
MAXCOUNT=1
let MAXCOUNT=COUNT_SF1+1


# pupil links
while [ $COUNT -lt $MAXCOUNT ] ; do
    # padding
    NUM=$(printf %03d $COUNT)
    # Date extract to button
    /bin/echo "<button class=\"accordion\">" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    $PUPPATH/pup --charset iso-8859-1 -f $S_SUBDIR/f1/subst_$NUM.htm 'div.mon_title' >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "</button>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "<div class=\"panel\">" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Headline is set to H4
    /bin/echo "<h4>Sch&uuml;ler/innen</h4>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "<p></p>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Table extraction
    $PUPPATH/pup --charset iso-8859-1 -f $S_SUBDIR/f1/subst_$NUM.htm 'table' >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "</div>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Warning
    /bin/echo $WARNMESS >> $OUTPUT_PATH/$OUTPUT_FILENAME
    let COUNT=COUNT+1
done

# Reset for second part of SuS 

/bin/echo "<h2 id=\"sus2\">$SUS2H</h2>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
COUNT=1
MAXCOUNT=1
let MAXCOUNT=COUNT_SF2+1

# pupil rechts
while [ $COUNT -lt $MAXCOUNT ] ; do
    # padding
    NUM=$(printf %03d $COUNT)
    # Date extract to button
    /bin/echo "<button class=\"accordion\">" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    $PUPPATH/pup --charset iso-8859-1 -f $S_SUBDIR/f2/subst_$NUM.htm 'div.mon_title' >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "</button>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "<div class=\"panel\">" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Headline is set to H3
    /bin/echo "<h4>Sch&uuml;ler/innen</h4>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "<p></p>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Table extraction
    $PUPPATH/pup --charset iso-8859-1 -f $S_SUBDIR/f2/subst_$NUM.htm 'table' >> $OUTPUT_PATH/$OUTPUT_FILENAME
    /bin/echo "</div>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
    # Warning
    /bin/echo $WARNMESS >> $OUTPUT_PATH/$OUTPUT_FILENAME
    let COUNT=COUNT+1
done

# create footer
/bin/echo "<p></p>" >> $OUTPUT_PATH/$OUTPUT_FILENAME
/bin/cat $WORKDIR/$FOOT_FILENAME >> $OUTPUT_PATH/$OUTPUT_FILENAME

# cleanup
/bin/chown www-data.www-data $OUTPUT_PATH/$OUTPUT_FILENAME
/bin/chmod 2750 $OUTPUT_PATH/$OUTPUT_FILENAME
# and we are done
exit 0

Cron ruft das Skript alle 20 Minuten auf. Schöner wäre natürlich, wenn das Skript nur liefe, wenn sich was ändert im WebDavs Share. Aber inotify und Freunde sind auch nicht ohne. So ist es simpel und scheint zu funktionieren, ohne viel Ressourcen zu fressen.

Update 23.08

Einige Fehler im Skript (meist die vergessenenen \ vor den „) behoben und mit Hilfe von JavaScript mehr Übersichtlichkeit im Output erzeugt.