#!/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. # # 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: # --device /dev/usb/lp0 Drucker vorgeben, statt danach zu fragen # --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 GERAET="" PORT=9100 # --- Schalter -------------------------------------------------------------- while [ $# -gt 0 ]; do case "$1" in --device) GERAET="${2:-}"; shift 2 ;; --port) PORT="${2:-9100}"; shift 2 ;; -h|--help) sed -n '2,22p' "$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; } # Fragt den Benutzer und gibt die Antwort auf stdout zurueck. # # Die Umleitung auf /dev/tty ist der Grund, warum auch # "curl ... | sudo bash" noch bedienbar bleibt: dort ist die Standardeingabe # die Pipe und nicht die Tastatur. Gibt es gar kein Terminal, wird die Vorgabe # genommen, statt in einer Endlosschleife zu haengen. frage() { local text="$1" vorgabe="${2:-}" antwort="" if [ -t 0 ]; then read -r -p "$text" antwort || true elif [ -e /dev/tty ]; then read -r -p "$text" antwort < /dev/tty || true fi printf '%s' "${antwort:-$vorgabe}" } # Klartextname eines USB-Druckers aus dem sysfs, damit in der Auswahl nicht # nur "lp0" steht, sondern "EPSON TM-T88IV". 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. Drucker auswaehlen # --------------------------------------------------------------------------- titel "2. Drucker" if [ -z "$GERAET" ]; then GEFUNDEN=() for d in /dev/usb/lp* /dev/lp*; do [ -e "$d" ] && GEFUNDEN+=("$d") 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 wird." GERAET=$(frage " Geraetepfad [/dev/usb/lp0]: " "/dev/usb/lp0") else echo for i in "${!GEFUNDEN[@]}"; do printf ' %d) %-16s %s\n' "$((i + 1))" "${GEFUNDEN[$i]}" "$(usb_name "${GEFUNDEN[$i]}" || echo '')" done printf ' a) anderer Pfad\n\n' WAHL=$(frage " Welcher Drucker? [1]: " "1") if [ "$WAHL" = "a" ] || [ "$WAHL" = "A" ]; then GERAET=$(frage " Geraetepfad: " "/dev/usb/lp0") elif [ "$WAHL" -ge 1 ] 2>/dev/null && [ "$WAHL" -le "${#GEFUNDEN[@]}" ]; then GERAET="${GEFUNDEN[$((WAHL - 1))]}" else fehler "Ungueltige Auswahl: $WAHL" fi fi fi punkt "gewaehlt: $GERAET" # --------------------------------------------------------------------------- # 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 { printf 'PRINT_TOKEN=%s\n' "$TOKEN" printf 'PRINT_DEVICE=%s\n' "$GERAET" 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 case "$ANTWORT" in *'"deviceExists": false'*|*'"deviceExists":false'*) warnung "Das Geraet $GERAET ist gerade nicht da - Kabel pruefen." ;; 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","device":"%s","hostHint":"%s"}' \ "$SETUP_CODE" "$PORT" "$TOKEN" "$GERAET" "$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 "Die Timer-App druckt ab jetzt auf diesen Pi." 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 "Drucker: $GERAET" 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