#!/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