Serverinstallation¶
Diese Anleitung richtet den Druckserver (tavletta serve) auf einem
Linux-Server für eine Firma ein: Andere Programme, zum Beispiel ein
Inventarprogramm, drucken darüber Etiketten im Netzwerk. Was der Server tut
und wie Programme ihn verwenden: Druckserver und
API-Referenz.
Überblick¶
- Ein kleiner Dienst auf Port 8790: eine REST-API (und MCP für KI-Assistenten). Er zeichnet die Etiketten selbst und schickt sie über das Netzwerk an die Drucker; kein Desktop, kein Browser, keine Datenbank.
- Die Drucker, die er verwenden darf, stehen in
printers.toml; auf keiner anderen Adresse kann gedruckt werden. - Jedes Programm und jedes Gerät bekommt ein eigenes Zugangstoken mit einer
Rolle (
tokens.toml); die Datei speichert von jedem Token nur einen Fingerabdruck (Hash). - Die Etiketten sind
.tavletta-Dateien in einem Etikettenordner, gestaltet in der Tavletta-App und dorthin kopiert. - Er lauscht nur auf
127.0.0.1. Andere Computer erreichen ihn über einen Reverse Proxy mit HTTPS (Traefik, nginx, Caddy).
Voraussetzungen¶
- Ein Linux-Server auf x86-64 oder ARM64 (eine virtuelle Maschine oder ein Raspberry Pi 4/5 genügt).
- Entweder Docker Engine mit dem Compose-Plugin (Variante A, empfohlen) oder Python 3.11 oder neuer mit PyGObject, Pango und Cairo aus der Distribution (Variante B).
- Ressourcen: 1 CPU-Kern, 1 GB RAM reserviert (der Server braucht meist 50–150 MB; ein grosses Etikett ist auf 40 Megapixel begrenzt), 500 MB Speicherplatz für das Image oder die Installation, dazu die Etiketten.
- Netzwerk vom Server zu den Druckern:
| Drucker | Port |
|---|---|
| cab, Brother QL, ZPL, EPL2, TSPL, ESC/POS, DYMO und andere, die "raw" drucken | TCP 9100 |
| Bürodrucker und CUPS-Warteschlangen (IPP) | TCP 631 |
| Brother VC-500W | TCP 9100 |
- Nur um Drucker automatisch zu finden (mDNS) oder für USB-Drucker: das
Netzwerk des Hosts (Variante A mit
compose.host.yaml) oder eine native Installation. - Schriften: Jede Schrift, die die Etiketten verwenden, muss auf dem Server installiert sein (siehe Schriften).
Variante A: Docker Compose¶
1. Dateien und Image holen¶
sudo git clone https://git.joshuahirsig.xyz/joshii/tavletta.git /opt/tavletta
cd /opt/tavletta
sudo git checkout v1.1.0 # the release to run: git tag lists them
cd packaging/docker
sudo docker compose pull # ghcr.io/joshii-h/tavletta:1.1, for x86-64 and ARM64
Das Image (etwa 230 MB), gebaut und getestet von der CI des Projekts und signiert (siehe Server und Container), enthält Tavletta, Python, Pango und Cairo, die Schriften DejaVu, Source Sans 3, Liberation (Masse von Arial, Times New Roman und Courier New) und Carlito (Masse von Calibri). Es läuft als Benutzer ohne Rechte, schreibgeschützt, ohne Capabilities.
2. Ordner¶
Alles, was zu Ihrer Installation gehört, liegt in drei Ordnern neben
compose.yaml (git ignoriert sie):
/opt/tavletta/packaging/docker/
├── compose.yaml
├── config/ printers.toml, tokens.toml
├── labels/ the label files (.tavletta) and their data files
└── fonts/ fonts of your own (.ttf, .otf), may stay empty
sudo mkdir -p config labels fonts
sudo chown -R 1000:1000 config labels fonts
Der Container läuft als Benutzer 1000 (TAVLETTA_UID und TAVLETTA_GID für
einen anderen setzen): Er liest config/ und speichert Zähler in die
Etikettendateien in labels/.
3. Drucker: printers.toml¶
config/printers.toml nennt jeden Drucker, den Programme verwenden
dürfen. Ein Beispiel mit einem cab-Drucker für Inventaretiketten
(Thermotransfer, um 180 Grad gedreht gedruckt), einem Brother QL am Empfang
und einem Bürodrucker über IPP:
[printers.it-cab]
backend = "cab" # cab EOS, SQUIX, MACH
host = "cab-it.example.com" # name or IP address of the printer
description = "Asset labels"
location = "IT store room"
media = "57 x 32 mm polyester, resin ribbon"
default = true # used when a request names no printer
max_copies = 50
[printers.it-cab.settings]
method = "transfer" # thermal transfer with ribbon (direct: without)
upside_down = true # printed turned by 180 degrees
[printers.reception-ql]
backend = "brother_ql" # Brother QL-700, QL-8xx, QL-1100 …
host = "192.0.2.21"
description = "Visitor and sample labels"
location = "Reception"
media = "DK-22205, 62 mm continuous"
max_copies = 20
cut = "full"
[printers.reception-ql.settings]
quality = "high"
[printers.office]
backend = "system" # any IPP printer or CUPS queue
host = "ipp://office-printer.example.com/ipp/print"
description = "Label sheets, A4"
max_copies = 10
backendist das Druckermodell:cab,brother_ql,brother_ql_diecut,ptouch,vc500w,zpl,epl,tspl,escpos,dymo,niimbot,system(tavletta list-printerszeigt sie).hostist der Name oder die IP-Adresse des Druckers; fürsystemeine IPP-Adresse. Geben Sie Druckern eine feste Adresse (DHCP-Reservierung oder DNS-Name).- Die Einstellungen eines Modells sind die der Druckereinstellungen der App; die vollständige Liste steht in docs/server.md.
- Ein Fehler hält den Server mit der Datei, dem Schlüssel und dem Problem
an, zum Beispiel
printers.toml: printers.it-cab.settings.method: expected one of: printer, transfer, direct. Änderungen brauchen einen Neustart.
4. Zugangstoken: tokens.toml¶
Erstellen Sie ein Token pro Programm oder Gerät. Das Token wird einmal gezeigt; die Datei speichert nur seinen Hash.
sudo docker run --rm --user 1000:1000 -v ./config:/config --entrypoint tavletta \
ghcr.io/joshii-h/tavletta:1.1 token new inventory-tool --role print --printer it-cab \
--description "Inventory tool" \
--tokens /config/tokens.toml --printers /config/printers.toml
tav_3f9c0a1b2c4d_Xq2…
tavletta: The token is shown only this once; /config/tokens.toml keeps only its hash.
- Geben Sie das Token dem Administrator des Programms über einen sicheren Weg (einen Passwortmanager), nie per E-Mail.
- Rollen:
preview(nur ansehen),print(auch drucken, die eigenen Aufträge sehen),admin(jeden Auftrag sehen und abbrechen). Programme bekommenprint, die Überwachungpreview. --printer(mehrfach möglich) beschränkt das Token auf diese Drucker,--expires 2027-12-31auf ein Datum.- Geben Sie immer
--printersan: Sobald ein Token auf Drucker beschränkt ist, lässt sich die Datei nur mit der Druckerliste prüfen. - Neue Tokens gelten sofort, ohne Neustart. Um eines zu widerrufen, löschen
Sie seinen Eintrag in
config/tokens.toml.
5. Etiketten¶
Gestalten Sie die Etiketten in der Tavletta-App und kopieren Sie die
.tavletta-Dateien nach labels/ (Unterordner sind erlaubt). Programme
nennen sie ohne Endung: labels/inventory/asset-tag.tavletta ist "label":
"inventory/asset-tag".
sudo cp asset-tag.tavletta labels/
sudo chown 1000:1000 labels/asset-tag.tavletta
6. Starten¶
sudo docker compose up -d
sudo docker compose ps
curl http://127.0.0.1:8790/api/v1/health
{"status": "ok", "version": "1.1.0"}
- Der Container startet nach einem Absturz oder einem Neustart des Servers von selbst wieder.
- Die Zeitzone von
{=date}und{=time}auf Etiketten stellen Sie mitTAVLETTA_TZ=Europe/Zurichin einer Datei.envnebencompose.yamlein (standardmässig UTC). - Token und Drucker prüfen:
TOKEN=tav_3f9c0a1b2c4d_Xq2…
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8790/api/v1/printers
Jeder Drucker wird mit seinem Zustand aufgeführt; UNREACHABLE mit einer
Meldung, wenn der Server ihn nicht erreicht.
Host-Netzwerk: mDNS und USB¶
Um Drucker im Netzwerk per mDNS zu finden, auf CUPS-Warteschlangen des Hosts
oder auf USB-Druckern zu drucken, starten Sie mit der Ergänzung
compose.host.yaml:
sudo docker compose -f compose.yaml -f compose.host.yaml up -d
Der Container teilt dann das Netzwerk des Hosts und lauscht auf
127.0.0.1:8790 des Hosts. CUPS-Warteschlangen des Hosts sind
ipp://localhost:631/printers/<queue> in printers.toml.
Protokoll¶
sudo docker compose logs -f
Jede Anfrage wird mit der Adresse des Clients, dem Namen des Tokens, dem Pfad und dem Status protokolliert; jeder Auftrag mit Etikett, Drucker und Ergebnis. Tokens und Etiketteninhalte werden nie protokolliert.
Aktualisieren¶
cd /opt/tavletta
sudo git fetch --tags
sudo git checkout v1.2.0 # the new release
cd packaging/docker
sudo docker compose pull
sudo docker compose up -d
Lesen Sie zuerst die Versionshinweise. Wartende Aufträge werden abgebrochen,
wenn der Server anhält; ein Etikett, das gerade gedruckt wird, wird fertig
gedruckt. config/, labels/ und fonts/ bleiben erhalten.
Sicherung¶
Sichern Sie die drei Ordner: config/ (die Druckerliste, die Token-Hashes),
labels/ (die Etiketten und ihre Zähler: Die nächste Nummer jedes Etiketts
mit {=counter} steht in seiner Datei) und fonts/. Das Image lässt sich
jederzeit neu herunterladen.
Deinstallieren¶
cd /opt/tavletta/packaging/docker
sudo docker compose down
sudo docker image rm ghcr.io/joshii-h/tavletta:1.1
sudo rm -r /opt/tavletta # also deletes config, labels and fonts
Variante B: nativ mit systemd¶
Für einen Server ohne Docker. Getestet auf Debian 13, Ubuntu 24.04 und Fedora 44. Die Installation braucht Internetzugang (Python-Pakete von PyPI, eines von GitHub).
1. Systempakete¶
Debian, Ubuntu:
sudo apt install python3 python3-venv python3-gi python3-gi-cairo gir1.2-pango-1.0 \
gettext git fontconfig fonts-dejavu-core
Fedora:
sudo dnf install python3 python3-gobject gobject-introspection pango gettext git-core
GTK ist nicht nötig. gettext und git braucht es nur während der
Installation.
2. Tavletta installieren¶
In eine eigene Python-Umgebung in /opt/tavletta, die das PyGObject des
Systems verwendet:
sudo python3 -m venv --system-site-packages /opt/tavletta
sudo /opt/tavletta/bin/pip install "git+https://git.joshuahirsig.xyz/joshii/tavletta.git@v1.1.0"
/opt/tavletta/bin/tavletta --version
3. Dienstbenutzer und Ordner¶
sudo useradd --system --home-dir /var/lib/tavletta --create-home \
--shell /usr/sbin/nologin tavletta
sudo install -d -o tavletta -g tavletta -m 0750 /etc/tavletta /var/lib/tavletta/labels
| Pfad | Inhalt | Besitzer, Rechte |
|---|---|---|
/etc/tavletta/printers.toml |
die Drucker (wie oben) | tavletta, 600 |
/etc/tavletta/tokens.toml |
die Token-Hashes | tavletta, 600 |
/var/lib/tavletta/labels/ |
die Etikettendateien | tavletta, 750 |
/usr/local/share/fonts/ |
eigene Schriften | root, für alle lesbar |
Erstellen Sie die Tokens als Dienstbenutzer, damit die Datei ihm gehört:
sudo -u tavletta /opt/tavletta/bin/tavletta token new inventory-tool --role print \
--printer it-cab --tokens /etc/tavletta/tokens.toml --printers /etc/tavletta/printers.toml
Der Server weist eine tokens.toml ab, die jeder Benutzer ändern darf, und
warnt bei einer, die andere lesen dürfen.
4. Der Dienst¶
packaging/systemd/tavletta-server.service führt den Server als tavletta
aus, abgeschottet (System schreibgeschützt, keine Home-Ordner, keine
Capabilities, 1 GB Speicher):
sudo curl -fsSLo /etc/systemd/system/tavletta-server.service \
https://git.joshuahirsig.xyz/joshii/tavletta/raw/tag/v1.1.0/packaging/systemd/tavletta-server.service
sudo systemctl daemon-reload
sudo systemctl enable --now tavletta-server
systemctl status tavletta-server
curl http://127.0.0.1:8790/api/v1/health
sudo systemctl reload tavletta-serverliesttokens.tomlneu ein (SIGHUP); die Datei wird auch bei jeder Änderung neu gelesen.sudo systemctl restart tavletta-servernach Änderungen anprinters.tomloder nach dem Installieren von Schriften.- Protokoll:
journalctl -u tavletta-server -f. - Die Zeitzone ist die des Servers (
timedatectl).
Für einen einzelnen Benutzer auf einem Desktop gibt es stattdessen den
Benutzerdienst packaging/systemd/tavletta.service.
Aktualisieren¶
sudo /opt/tavletta/bin/pip install --upgrade "git+https://git.joshuahirsig.xyz/joshii/tavletta.git@v1.2.0"
sudo curl -fsSLo /etc/systemd/system/tavletta-server.service \
https://git.joshuahirsig.xyz/joshii/tavletta/raw/tag/v1.2.0/packaging/systemd/tavletta-server.service
sudo systemctl daemon-reload
sudo systemctl restart tavletta-server
Sichern Sie /etc/tavletta, /var/lib/tavletta/labels und Ihre Schriften.
Reverse Proxy und HTTPS¶
Der Server spricht einfaches HTTP auf 127.0.0.1. Andere Computer erreichen
ihn über einen Reverse Proxy, der HTTPS hinzufügt. Geben Sie dem Server die
Adresse des Proxys mit --trusted-proxy, damit das Protokoll und die
Grenzen pro Client den echten Client sehen, nicht den Proxy.
Traefik (Docker)¶
Traefik und Tavletta in einem Docker-Netzwerk, Traefik mit einer festen
Adresse. Entfernen Sie in compose.yaml den Eintrag ports: (der Server
ist dann nur über Traefik erreichbar) und ergänzen Sie:
services:
tavletta:
# ... as in compose.yaml, without ports:
networks: [proxy]
command:
- "--mcp"
- "--printers"
- "/config/printers.toml"
- "--tokens"
- "/config/tokens.toml"
- "--trusted-proxy"
- "172.30.0.10/32" # Traefik's address in the network
labels:
- "traefik.enable=true"
- "traefik.http.routers.tavletta.rule=Host(`labels.example.com`)"
- "traefik.http.routers.tavletta.entrypoints=websecure"
- "traefik.http.routers.tavletta.tls.certresolver=letsencrypt"
- "traefik.http.services.tavletta.loadbalancer.server.port=8790"
networks:
proxy:
external: true # Traefik's network, Traefik at 172.30.0.10
nginx¶
Der Server auf 127.0.0.1 beantwortet nur Anfragen für einen lokalen
Hostnamen (gegen DNS-Rebinding): nginx darf den öffentlichen Namen nicht
weitergeben. Seine Voreinstellung (127.0.0.1:8790) ist richtig; fügen Sie
kein proxy_set_header Host $host hinzu.
server {
listen 443 ssl;
server_name labels.example.com;
ssl_certificate /etc/ssl/certs/labels.example.com.pem;
ssl_certificate_key /etc/ssl/private/labels.example.com.key;
client_max_body_size 4m; # as the server's --max-body
location / {
proxy_pass http://127.0.0.1:8790;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 330s; # print requests may wait up to 300 s
}
}
Starten Sie den Server mit --trusted-proxy 127.0.0.1/32 (an ExecStart im
Dienst oder an command: in compose.yaml anhängen).
Caddy¶
Caddy gibt standardmässig den öffentlichen Hostnamen weiter; setzen Sie ihn auf den des Servers:
labels.example.com {
reverse_proxy 127.0.0.1:8790 {
header_up Host {upstream_hostport}
}
}
Caddy holt sich selbst ein Zertifikat. Starten Sie den Server mit
--trusted-proxy 127.0.0.1/32.
Firewall¶
- Öffnen Sie nur den Port 443 des Proxys, und nur für die Netzwerke, die ihn brauchen (den Server des Inventarprogramms, das VLAN der Clients).
- Port 8790 bleibt geschlossen: Der Server lauscht auf
127.0.0.1(Docker veröffentlicht ihn nur auf127.0.0.1). - Ausgehend vom Server zu den Druckern: TCP 9100 und 631.
- Machen Sie den Server nie aus dem Internet erreichbar.
Schriften¶
Ein Etikett wird mit den Schriften des Computers gezeichnet, der es
druckt. Ein Text in einer Schrift, die der Server nicht hat, wird in einer
anderen gezeichnet; die Vorschau und der Druckauftrag warnen dann mit
font_missing (zum Beispiel Element 2: the font Source Sans 3 is not
installed – another font is used).
- Docker: Das Image hat DejaVu, Source Sans 3, Liberation und
Carlito. Kopieren Sie andere Schriften (
.ttf,.otf, z. B. die Hausschrift) nachfonts/und starten Sie neu:sudo docker compose restart. Weitere Schriftpakete von Debian: das Image selbst bauen,sudo docker compose -f compose.yaml -f compose.build.yaml build --build-arg FONTS="fonts-dejavu-core fonts-adobe-sourcesans3 fonts-liberation fonts-crosextra-carlito fonts-noto-core", danachup -dmit denselben zwei Dateien. - Nativ: Installieren Sie die Schriftpakete der Distribution (Debian,
Ubuntu:
fonts-adobe-sourcesans3; Fedora:adobe-source-sans-pro-fonts) oder kopieren Sie die Schriftdateien nach/usr/local/share/fonts/, dannsudo fc-cache -fund den Dienst neu starten. - Prüfen, was der Server hat:
fc-list : family(im Container:sudo docker compose exec tavletta fc-list : family). - Schriften brauchen für die Verwendung auf einem Server unter Umständen eine Lizenz; Source Sans 3, Liberation, Carlito, DejaVu und Noto sind frei.
Sicherheits-Checkliste¶
- Ein Token pro Programm oder Gerät, mit der kleinsten Rolle (
printfür Programme,previewfür die Überwachung) und beschränkt auf seine Drucker (--printer); ein Ablaufdatum für befristete. - Tokens in einem Passwortmanager aufbewahrt;
tokens.tomlmit Rechten 600. printers.tomlin Gebrauch: Programme können nur auf diesen Druckern drucken.max_copiespro Drucker auf ein sinnvolles Mass gesetzt.- Der Server nur über den HTTPS-Proxy erreichbar, nur aus den Netzwerken, die ihn brauchen; nie aus dem Internet.
--trusted-proxyauf die eigene Adresse des Proxys gesetzt, nichts Weiteres.- Aktualisierungen: die Versionen verfolgen und innert Wochen aktualisieren;
das Image jeder Version bringt die Sicherheitsaktualisierungen von Debian
ihres Tages mit (
docker compose pull). - Sicherung von
config/(oder/etc/tavletta) und der Etiketten.
Die Einzelheiten jedes Schutzes (Grenzen, Sperre bei falschen Tokens, Dateizugriff, Webseiten): docs/server.md.
Fehlerbehebung¶
| Symptom | Ursache und Abhilfe |
|---|---|
Ein Drucker ist UNREACHABLE (printer_timeout, printer_not_found) |
Der Server erreicht ihn nicht: vom Server aus mit nc -vz <printer> 9100 testen (631 für IPP); Adresse und Firewall prüfen, und ob der Drucker eingeschaltet ist. In Docker muss der Container das Netzwerk des Druckers erreichen. |
401 unauthorized |
Token fehlt, ist falsch geschrieben, abgelaufen oder aus tokens.toml entfernt. Der Header lautet Authorization: Bearer <token>. |
429 too_many_requests |
Zehn falsche Tokens von dieser Adresse innerhalb einer Minute: das Token korrigieren, eine Minute warten. |
429 queue_full |
100 Aufträge (20 pro Token) warten: Der Drucker ist langsam oder steht; ihn prüfen, später wiederholen. |
503 busy |
Alle Anfrage- oder Zeichenplätze sind gerade belegt: nach ein, zwei Sekunden wiederholen. |
404 printer_not_found |
Nicht in printers.toml, oder das Token darf ihn nicht verwenden (--printer). |
413 label_too_large |
Das Etikett hat zu viele Elemente, zu lange Texte oder wird grösser als 40 Megapixel: es vereinfachen oder weniger Datenzeilen pro Anfrage schicken. |
421 wrong_host |
Ein Reverse Proxy gibt den öffentlichen Hostnamen an einen Server auf 127.0.0.1 weiter: siehe nginx und Caddy. |
font_missing in den Warnungen, der Text sieht falsch aus |
Die Schrift ist auf dem Server nicht installiert: siehe Schriften. |
409 counter_not_saved |
Die Etikettendatei ist für den Benutzer des Servers nicht beschreibbar: mit chown diesem Benutzer geben (Docker: 1000, nativ: tavletta). |
| Datum oder Uhrzeit auf Etiketten ein, zwei Stunden daneben | Der Container läuft auf UTC: TAVLETTA_TZ=Europe/Zurich in .env setzen, dann docker compose up -d. |
| Der Server startet nicht | docker compose logs oder journalctl -u tavletta-server: Ein Fehler in printers.toml oder tokens.toml wird mit seinem Schlüssel genannt. |
| Zugriff auf die Ordner verweigert (Fedora, RHEL mit SELinux und Podman) | :Z an die Volumes in compose.yaml anhängen. |