#!/bin/bash # --------------------------------------------------------------------------- # Bondrucker-Bruecke einrichten - wird AUF DEM PI ausgefuehrt. # # Dieses Skript wird nicht von Hand gepflegt heruntergeladen, sondern von der # Timer-App ausgeliefert: sie setzt ihre eigene Adresse und einen einmaligen # Einrichtungscode ein und haengt den Druckdienst sowie die systemd-Unit # gleich mit an. Heraus kommt eine einzige Datei, die alles enthaelt. # # Aufruf auf dem Pi: # curl -fsSL "" -o bondrucker.sh # sudo bash bondrucker.sh # # Der Link steht in der Timer-App unter Einstellungen -> Drucker einrichten. # # Dieses Skript installiert nur die Software. WELCHER Drucker bedient wird, # wird anschliessend in der Timer-App ausgewaehlt - sie fragt den Pi, was # angeschlossen ist. Hier ist dazu nichts einzustellen. # # Ohne Netz zur Timer-App laeuft das Skript trotzdem durch; es zeigt am Ende # Adresse und Token an, die dann von Hand in die Einstellungen gehoeren. # # Schalter: # --port 9100 Lausch-Port der Bruecke # --------------------------------------------------------------------------- set -euo pipefail # Diese beiden Werte ersetzt die Timer-App beim Ausliefern. APP_URL="@@APP_URL@@" SETUP_CODE="@@SETUP_CODE@@" ZIEL=/opt/bondrucker PORT=9100 # --- Schalter -------------------------------------------------------------- while [ $# -gt 0 ]; do case "$1" in --port) PORT="${2:-9100}"; shift 2 ;; -h|--help) sed -n '2,26p' "$0"; exit 0 ;; *) echo "Unbekannter Schalter: $1" >&2; exit 1 ;; esac done if [ "$(id -u)" -ne 0 ]; then echo "Bitte mit sudo starten: sudo bash $0" >&2 exit 1 fi # --------------------------------------------------------------------------- # Hilfsmittel # --------------------------------------------------------------------------- titel() { printf '\n\033[1m%s\033[0m\n' "$1"; } punkt() { printf ' %s\n' "$1"; } warnung() { printf ' \033[33m%s\033[0m\n' "$1"; } fehler() { printf '\n\033[31m%s\033[0m\n' "$1" >&2; exit 1; } # Klartextname eines USB-Druckers aus dem sysfs. Nur zur Anzeige - ausgewaehlt # wird spaeter in der Timer-App, die dieselbe Liste ueber /devices abfragt. usb_name() { local pfad pfad=$(readlink -f "/sys/class/usbmisc/$(basename "$1")/device" 2>/dev/null) || return 1 while [ -n "$pfad" ] && [ "$pfad" != "/" ]; do if [ -r "$pfad/product" ]; then printf '%s %s' "$(cat "$pfad/manufacturer" 2>/dev/null || true)" "$(cat "$pfad/product")" return 0 fi pfad=$(dirname "$pfad") done return 1 } # --------------------------------------------------------------------------- # 1. System pruefen # --------------------------------------------------------------------------- titel "1. System" command -v python3 >/dev/null || fehler "python3 fehlt. Bitte nachinstallieren: apt install python3" command -v systemctl >/dev/null || fehler "systemd fehlt - dieses Skript richtet einen systemd-Dienst ein." punkt "$(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME" || uname -s) ($(uname -m))" punkt "$(python3 --version)" # --------------------------------------------------------------------------- # 2. Angeschlossene Drucker anzeigen # --------------------------------------------------------------------------- titel "2. Angeschlossene Drucker" gefunden=0 for d in /dev/usb/lp* /dev/lp*; do [ -e "$d" ] || continue gefunden=$((gefunden + 1)) printf ' %-16s %s\n' "$d" "$(usb_name "$d" || echo '')" done if [ "$gefunden" -eq 0 ]; then warnung "Gerade haengt kein Drucker unter /dev/usb/lp* am Pi." warnung "Das ist kein Hindernis: der Dienst findet ihn, sobald er angesteckt" warnung "wird. Danach in der Timer-App die Liste neu abrufen." else echo punkt "Welcher davon genommen wird, wird in der Timer-App ausgewaehlt." fi # --------------------------------------------------------------------------- # 3. Dienstbenutzer # --------------------------------------------------------------------------- titel "3. Dienstbenutzer" if ! id bondrucker >/dev/null 2>&1; then useradd --system --no-create-home --shell /usr/sbin/nologin --groups lp bondrucker punkt "Benutzer 'bondrucker' angelegt (Gruppe lp)." else usermod -aG lp bondrucker punkt "Benutzer 'bondrucker' war schon da." fi # --------------------------------------------------------------------------- # 4. Programmdateien # --------------------------------------------------------------------------- titel "4. Programmdateien" install -d -m 755 "$ZIEL" cat > "$ZIEL/print-server.py" <<'PRINT_SERVER_ENDE' @@PRINT_SERVER@@ PRINT_SERVER_ENDE chmod 755 "$ZIEL/print-server.py" punkt "$ZIEL/print-server.py" cat > /etc/systemd/system/bondrucker.service <<'SERVICE_ENDE' @@SERVICE@@ SERVICE_ENDE chmod 644 /etc/systemd/system/bondrucker.service punkt "/etc/systemd/system/bondrucker.service" # --------------------------------------------------------------------------- # 5. Konfiguration # --------------------------------------------------------------------------- titel "5. Konfiguration" TOKEN="$(head -c 24 /dev/urandom | base64 | tr -d '/+=' | head -c 32)" install -d -m 750 /etc/bondrucker # Bewusst OHNE PRINT_DEVICE: welcher Drucker bedient wird, schickt die # Timer-App bei jedem Auftrag mit. Stuende die Wahl auch hier, gaebe es zwei # Stellen, die es zu wissen glauben - und eine davon waere irgendwann falsch. { printf 'PRINT_TOKEN=%s\n' "$TOKEN" printf 'PRINT_PORT=%s\n' "$PORT" } > /etc/bondrucker/env chmod 640 /etc/bondrucker/env chown root:bondrucker /etc/bondrucker/env punkt "/etc/bondrucker/env geschrieben, neues Token erzeugt." # Ohne diese Regel gehoert /dev/usb/lp0 nach jedem Einstecken wieder root # allein - der Dienst laeuft aber nicht als root und koennte nicht drucken. cat > /etc/udev/rules.d/99-bondrucker.rules <<'RULE' # USB-Bondrucker: Gruppe lp und Schreibrecht, damit der Dienst 'bondrucker' # ohne root drucken kann. Gilt fuer alle usblp-Geraete. SUBSYSTEM=="usbmisc", KERNEL=="lp[0-9]*", GROUP="lp", MODE="0660" SUBSYSTEM=="usb", ATTR{bInterfaceClass}=="07", GROUP="lp", MODE="0660" RULE udevadm control --reload-rules >/dev/null 2>&1 || true udevadm trigger --subsystem-match=usbmisc >/dev/null 2>&1 || true punkt "udev-Regel gesetzt (Geraet ueberlebt das Aus- und Einstecken)." # --------------------------------------------------------------------------- # 6. Dienst starten # --------------------------------------------------------------------------- titel "6. Dienst" systemctl daemon-reload systemctl enable bondrucker.service >/dev/null 2>&1 # Bewusst "restart" und nicht "enable --now": bei einer erneuten Einrichtung # laeuft der Dienst schon, und "enable --now" wuerde ihn in Ruhe lassen. Er # liefe dann mit dem ALTEN Token weiter und wuerde jeden Bon ablehnen. systemctl restart bondrucker.service sleep 1 if ! systemctl is-active --quiet bondrucker.service; then systemctl --no-pager --lines=20 status bondrucker.service || true fehler "Der Dienst laeuft nicht. Die Ausgabe oben sagt, woran es liegt." fi punkt "bondrucker.service laeuft." # Gegenprobe von innen: antwortet die Bruecke, und nimmt sie das neue Token? ANTWORT="$(curl -fsS -m 5 -H "X-Print-Token: $TOKEN" "http://127.0.0.1:$PORT/status" 2>/dev/null || true)" case "$ANTWORT" in *'"tokenAccepted": true'*|*'"tokenAccepted":true'*) punkt "Bruecke antwortet und nimmt das Token an." ;; "") warnung "Die Bruecke antwortet auf 127.0.0.1:$PORT noch nicht." ;; *) warnung "Die Bruecke antwortet, lehnt das Token aber ab: $ANTWORT" ;; esac # --------------------------------------------------------------------------- # 7. Bei der Timer-App anmelden # --------------------------------------------------------------------------- IP="$(hostname -I 2>/dev/null | awk '{print $1}')" titel "7. Timer-App" angemeldet=0 case "$APP_URL" in @@*|"") punkt "Kein Einrichtungscode enthalten - dieses Skript wurde nicht ueber" punkt "die Timer-App geladen. Anmeldung wird uebersprungen." ;; *) if ! command -v curl >/dev/null; then warnung "curl fehlt - die Anmeldung muss von Hand erfolgen." else RUMPF="$(printf '{"code":"%s","port":%s,"token":"%s","hostHint":"%s"}' \ "$SETUP_CODE" "$PORT" "$TOKEN" "$IP")" if ERGEBNIS="$(curl -fsS -m 15 -X POST \ -H 'Content-Type: application/json' \ -d "$RUMPF" "$APP_URL/api/setup/register" 2>&1)"; then punkt "Bei $APP_URL angemeldet." punkt "Jetzt dort unter Einstellungen den Drucker auswaehlen." angemeldet=1 else warnung "Anmeldung fehlgeschlagen: $ERGEBNIS" warnung "Meist ist der Code abgelaufen - in der Timer-App einen neuen erzeugen." fi fi ;; esac # --------------------------------------------------------------------------- # Fertig # --------------------------------------------------------------------------- titel "Fertig" punkt "Adresse: ${IP:-}" punkt "Port: $PORT" if [ "$angemeldet" -eq 0 ]; then echo punkt "Von Hand eintragen in der Timer-App unter Einstellungen:" printf ' Adresse: %s\n Port: %s\n Token: %s\n' "${IP:-?}" "$PORT" "$TOKEN" fi echo punkt "Zustand abfragen: curl -s http://${IP:-127.0.0.1}:$PORT/status" punkt "Protokoll: journalctl -u bondrucker -f" echo