Skip to content

Architecture

Three custom container images plus adopted upstream images. The Pi runs only Docker; everything functional lives in containers.

Components

Component Role Language
scan-bridge Core daemon, REST API, profile dispatch, metrics Go
sane-runtime SANE drivers, USB integration Bash + Go
scan-processor Image processing, PDF assembly, NFS write Go
Paperless-ngx DMS with OCR, indexing, UI upstream
scanservjs Optional manual scanning web UI upstream

Only the first three are built in this repository. Everything else is an adopted upstream image — this project ships compose files and configuration for them, never forks.

Data flow

sequenceDiagram
    autonumber
    participant T as Trigger
    participant B as scan-bridge
    participant S as sane-runtime
    participant P as scan-processor
    participant N as Synology NAS
    participant D as Paperless-ngx

    T->>B: POST /scan {profile}
    B->>B: resolve profile, enqueue job
    B->>S: SANE-net scan request
    S->>S: scanimage via avision backend
    S-->>B: raw TIFF/JPEG batch
    B->>P: pages + profile metadata
    P->>P: deskew, drop blank pages, assemble PDF
    P->>N: atomic write to the consume directory
    N-->>D: pickup via inotify or polling
    D->>D: OCR, index, apply tags

Everything between the trigger and the final PDF is containerized. Only the USB device node and the NFS mount cross the host-container boundary.

The trigger is any HTTP client: a Zigbee remote routed through Home Assistant, an n8n workflow, a web UI, or plain curl. The scanner is reached through /dev/bus/usb on the host, passed into sane-runtime by a udev rule — never with --privileged.

Design principles

Container-first, host-thin. The Pi gets Docker, an NFS mount from /etc/fstab, and udev rules. Nothing else. If a feature appears to need a host-level installation, the containerized alternative is preferred.

Three custom images, no more. Scope discipline: anything that already exists upstream is adopted, not rebuilt.

Synology is the single source of truth. The Pi is an ingestion node. Losing the Pi loses no documents.

No cloud dependencies on the core path. Everything works with the network cable to the internet unplugged. Optional integrations are labelled as such.

No latest tags. Compose files pin specific versions; the update bot proposes bumps.

Trigger paths

The scan endpoint is deliberately trigger-agnostic — it accepts a profile name and nothing else. That keeps every trigger source interchangeable:

  • HTTP webhook — the primary path, and what everything else is built on
  • Zigbee remote via Home Assistant — a blueprint maps button positions to profiles
  • n8n workflow — the alternative automation path
  • Hardware scanner buttons — device-dependent and, on the reference scanner, only partially available; see the i1120 page

Storage

Three topologies are supported, each with different latency and backup characteristics. See Storage topologies for the comparison.

Privacy of the documentation site

The site itself follows the same no-cloud rule: it makes no third-party requests. Mermaid is self-hosted instead of loaded from a CDN and Google Fonts is off. See No third-party requests.

Further reading

The full architectural discussion, including the backup architecture, the high-availability stance, and the threat-model summary, lives in ARCHITECTURE.md in the repository.