Deploy auf den Timer-Server / deploy (push) Successful in 20s
Das Einrichtungsskript fragte bisher am Pi, welcher Drucker bedient werden soll. Damit stand die Einstellung an einem Ort, an den man nur mit einem Terminal kommt - und ein zweiter Drucker am Pi bedeutete: Skript nochmal. Jetzt fragt die App den Pi, was angeschlossen ist, und der Bediener waehlt in den Einstellungen. Die Bruecke bekam dafuer /devices; sie liest die Klartextnamen aus dem sysfs, damit dort "EPSON TM-T88IV" steht und nicht "lp0" - bei zwei Druckern der Unterschied zwischen Wissen und Raten. Der gewaehlte Pfad geht bei jedem Auftrag im Kopf X-Print-Device mit. Die Bruecke haelt dazu bewusst keinen Zustand und das Skript schreibt kein PRINT_DEVICE mehr: es soll genau eine Stelle geben, die weiss, welcher Drucker gemeint ist, naemlich die, an der es bedient wird. Leer bleibt "automatisch" - das erste gefundene Geraet, zugleich der Rueckfall, wenn aus lp0 nach dem Umstecken ein lp1 geworden ist. Der Pfad kommt damit von aussen und wird an beiden Enden geprueft, in PUT /api/settings und noch einmal in der Bruecke. Eine Schranke, die nur an einem Ende steht, ist keine: ohne die Pruefung auf dem Pi liesse sich der Dienst dazu bringen, in eine beliebige Datei zu schreiben. Nebenbei: ein nicht bereiter Drucker meldete sich in der Oberflaeche als "HTTP 503". Das verriet niemandem, dass nur ein Stecker lose ist. Die 503 der Bruecke ist kein Protokollfehler, und ihr Rumpf sagt, was fehlt - der wird jetzt ausgewertet. Geprueft am echten Pi (Debian 13, TM-T88IV): Einrichtung ohne Rueckfrage, Geraeteliste mit Namen, ausdrueckliche Wahl, Druck darauf, Rueckkehr auf automatisch, Druck darauf. Dazu die Abweise: /etc/passwd und ein Pfad mit angehaengtem Befehl werden zurueckgewiesen, ein erlaubtes aber fehlendes /dev/usb/lp7 ergibt eine Meldung im Klartext. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
254 lines
9.3 KiB
Bash
Executable File
254 lines
9.3 KiB
Bash
Executable File
#!/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 "<Link aus der Timer-App>" -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:-<IP des Pi>}"
|
|
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
|