Skip to content

Shopee Print — Operations Runbook

Service: shopee-auto-print on CT 108 (shopee-print, 192.168.0.58) Last updated: 2026-05-16

This runbook covers day-to-day operations for the shopee-auto-print service. For the initial deployment procedure, see Shopee Print (CT 108).


Check Service Health

# From the PVE host
pct exec 108 -- systemctl status shopee-auto-print

Expected output: active (running). If you see failed or activating (auto-restart), proceed to the journal logs:

pct exec 108 -- journalctl -u shopee-auto-print -n 50

Quick API liveness check (confirms the Fastify server is accepting requests):

pct exec 108 -- curl -s http://127.0.0.1:8787/api/jobs | jq .

A JSON response (even an empty array) means the service is up. A Connection refused means the process is not running.


Restart the Service

pct exec 108 -- systemctl restart shopee-auto-print
pct exec 108 -- systemctl status shopee-auto-print

The service is configured with Restart=on-failure and RestartSec=5, so transient crashes self-heal automatically. A manual restart is appropriate when:

  • You just deployed a new build.
  • The printer device became stale after a power cycle (see Printer Troubleshooting below).
  • Journal logs show a non-transient error you've resolved.

View the Print Queue

pct exec 108 -- curl -s http://127.0.0.1:8787/api/jobs | jq .

Each job has a state field following this state machine:

pending → fetching → printing → printed
                              → failed
                              → cancelled

Filter by state:

# Pending jobs only
pct exec 108 -- curl -s http://127.0.0.1:8787/api/jobs | jq '[.[] | select(.state == "pending")]'

# Failed jobs
pct exec 108 -- curl -s http://127.0.0.1:8787/api/jobs | jq '[.[] | select(.state == "failed")]'

# Jobs stuck in fetching (shouldn't persist across a service restart)
pct exec 108 -- curl -s http://127.0.0.1:8787/api/jobs | jq '[.[] | select(.state == "fetching")]'

Force-Print a Stuck Job

If a job is stuck in pending or failed state and you want to trigger an immediate print attempt regardless of the time window:

# Replace <id> with the numeric job ID from /api/jobs
pct exec 108 -- curl -s -X POST http://127.0.0.1:8787/api/jobs/<id>/force-print | jq .

M4 prerequisite

The force-print endpoint is wired in from M3 onwards. If the service is running a pre-M4 build, the dispatcher does not yet fetch the waybill PDF and call printPdf automatically — force-print will only transition the job state. Confirm the build version before relying on this.

If the dashboard is accessible at https://shopee-print.exzentcg.com, you can also use the Jobs page "Force print" button directly.


Printer Troubleshooting

Printer not detected (/dev/usb/lp0 missing inside CT)

Symptoms: print jobs fail with a write error to /dev/usb/lp0; ls /dev/usb/ inside the CT shows nothing.

Cause: The printer was power-cycled or unplugged while CT 108 was running. The bind-mount inode inside the CT becomes stale; the kernel does not automatically re-bind.

Fix — trigger udev and restart CT:

# On the PVE host — confirm the device is visible on the host first
ls -la /dev/usb/lp0   # expect: crw-rw-rw- ... 180, 0

# If the device is missing on the host (printer still off or disconnected):
# Power on / reconnect the printer first, then run:
udevadm trigger --subsystem-match=usb

# Verify the device is back on the host with correct permissions
ls -la /dev/usb/lp0   # expect: crw-rw-rw- (MODE="0666" from udev rule)

# Restart CT 108 to re-establish the bind-mount
pct stop 108
pct start 108

# Verify the device is visible inside the CT
pct exec 108 -- ls -la /dev/usb/lp0

Service must be running after CT restart

Confirm systemctl status shopee-auto-print is active (running) after the CT restarts. The service is set to start on boot (WantedBy=multi-user.target, systemctl enabled), but verify anyway.

If the printer runs but labels come out blank or fully black, the TSPL bitmap polarity is wrong. For the Fujun 9250: bit 0 = print (black), meaning set bits correspond to white pixels in the packed buffer. This is documented in src/printer/bitmap.ts. Do not change the polarity without re-testing against a real label — different TSPL printers may differ.

Check whether the label size in the TSPL header matches the physical media. The service uses SIZE 100 mm, 150 mm / GAP 2 mm, 0 mm (100×150mm gapped labels at 203 dpi). If you have swapped label rolls, update the size constants in src/printer/tspl.ts and redeploy.

Printer device permissions error

If logs show EACCES: permission denied, open '/dev/usb/lp0':

# On the PVE host — check the device permissions
ls -la /dev/usb/lp0

# If MODE is not 0666, re-trigger udev
udevadm control --reload-rules
udevadm trigger

# Verify
ls -la /dev/usb/lp0   # expect: crw-rw-rw-

If the udev rule is missing from the host, re-create it:

cat > /etc/udev/rules.d/99-shopee-printer.rules <<'EOF'
SUBSYSTEM=="usb", ATTRS{idVendor}=="0416", ATTRS{idProduct}=="5011", MODE="0666"
EOF
udevadm control --reload-rules
udevadm trigger

Deploy a Code Update

Use this procedure whenever a new version of shopee-auto-print is built and ready to ship to CT 108.

# Step 1 — Build on dev-shell (CT 107)
ssh dev@dev-shell
cd ~/Documents/shopee-auto-print
git pull
yarn build
cd web && yarn build && cd ..

# Step 2 — Package (never include .env or data.db)
tar czf /tmp/shopee-auto-print.tar.gz \
  dist/ \
  web/dist/ \
  node_modules/ \
  package.json

# Step 3 — Copy to PVE host
scp /tmp/shopee-auto-print.tar.gz root@pve:/tmp/

# Step 4 — Stop service, extract, restart
ssh root@pve 'pct exec 108 -- bash -c "
  systemctl stop shopee-auto-print &&
  tar xzf /tmp/shopee-auto-print.tar.gz -C /opt/shopee-auto-print &&
  rm /tmp/shopee-auto-print.tar.gz &&
  systemctl start shopee-auto-print
"'

# Step 5 — Verify
ssh root@pve 'pct exec 108 -- systemctl status shopee-auto-print'
ssh root@pve 'pct exec 108 -- curl -s http://127.0.0.1:8787/api/jobs | jq .'

.env and data.db are never touched by this procedure

Credentials and the print queue survive the upgrade. The SQLite file lives at /opt/shopee-auto-print/data.db. Never include it in the archive — it would overwrite live data.


Disaster Recovery (CT 108 lost)

If CT 108 is deleted, corrupted, or the PVE host is reprovisioned, follow these steps to restore the service.

Retrieve MASTER_KEY from 1Password first

Without the production MASTER_KEY, all Shopee credentials encrypted in the SQLite DB are unrecoverable. If the DB backup is intact, the key is needed to decrypt it. If no DB backup exists, the key is still needed so you can re-enter credentials with the same key (avoiding a new key rotation). Find it in 1Password under "shopee-auto-print MASTER_KEY (prod)".

Step 1 — Restore from SQLite backup (if available)

If a backup of /opt/shopee-auto-print/data.db was taken (e.g. via a Proxmox snapshot or a manual scp to another host), restore it after reprovision. The DB contains the encrypted credentials and the historical jobs table.

# Copy backup DB into the new CT after provisioning
scp data.db.bak root@pve:/tmp/
ssh root@pve 'pct push 108 /tmp/data.db.bak /opt/shopee-auto-print/data.db'

Step 2 — Reprovision CT 108

Follow the full Provisioning Steps in the service deployment doc:

  1. Create LXC with pct create 108 ...
  2. Install runtime dependencies (Node 22, poppler-utils)
  3. Add USB cgroup passthrough to /etc/pve/lxc/108.conf
  4. Deploy the service archive from dev-shell
  5. Write .env with the MASTER_KEY retrieved from 1Password
  6. Install and enable the systemd service

Step 3 — Restore or re-enter Shopee credentials

If the DB backup was restored: Start the service and verify via the dashboard that the shop is listed as connected and the token expiry is in the future. If the access token has expired, use the dashboard to re-authorize (OAuth flow from step 3 in the manual steps).

If no DB backup: Visit the dashboard, enter Shopee Partner ID and Partner Key, click "Connect to Shopee", and complete the OAuth flow. The new tokens will be encrypted with the MASTER_KEY from .env.

Step 4 — Complete the manual networking steps

If CT 108 receives a new IP (it shouldn't — 192.168.0.58 is statically assigned in the pct create command), update the NPM proxy host to point to the new IP. Otherwise all five manual steps should still be valid from the original deployment.

Step 5 — Verify end-to-end

# Service running
pct exec 108 -- systemctl status shopee-auto-print

# API responding
pct exec 108 -- curl -s http://127.0.0.1:8787/api/jobs | jq .

# Public endpoint (after NPM + Cloudflare steps are confirmed)
curl -s https://shopee-print.exzentcg.com/api/jobs | jq .

Useful Reference

Item Value
CT ID 108
LAN IP 192.168.0.58
Service port 8787
Service unit shopee-auto-print.service
Deploy path /opt/shopee-auto-print/
SQLite DB /opt/shopee-auto-print/data.db
Env file /opt/shopee-auto-print/.env
USB device (host) /dev/usb/lp0 (major 180, Winbond 0416:5011)
udev rule (host) /etc/udev/rules.d/99-shopee-printer.rules
MASTER_KEY location 1Password → "shopee-auto-print MASTER_KEY (prod)"
Deployment doc homelab/05_service_deployments/shopee-print.md
Venture repo github.com/ExzenTCG/shopee-auto-print