Skip to content

Repository files navigation

Nooky

Nooky is a small, LAN-only web app that downloads a direct EPUB URL, validates the file, queues it durably, and streams it over SSH to a rooted NOOK Simple Touch when the reader is awake. It uses SQLite from Python's standard library and needs no Redis, Celery, message broker, or JavaScript build pipeline.

What it does

  • Accepts direct EPUB links or book pages containing explicit EPUB links.
  • Streams HTTP and HTTPS downloads to a temporary file with a size limit.
  • Resolves DNS before connecting, rejects every non-public answer, pins the connection to the validated IP, and repeats the check for every redirect.
  • Validates the ZIP mimetype and META-INF/container.xml EPUB entries.
  • Selects and sanitizes a readable filename without allowing path traversal.
  • Uses the legacy algorithms required by the NOOK, but pins its RSA host key.
  • Stores queued EPUBs and job state on a persistent Docker volume.
  • A dedicated worker checks waiting jobs every 15 seconds, survives restarts, streams books through SSH, and confirms the remote byte count afterward.
  • After verification, sends a targeted Android media-scan broadcast for the new EPUB so it appears in the NOOK library without a reboot or storage remount.
  • Provides a responsive iPhone UI, confirmation route for Share Sheet shortcuts, a small PWA manifest, upload fallback, status, and health routes.

Using Nooky

Nooky interface showing URL entry, NOOK status, and queued and delivered books

  1. Paste a book page or direct .epub URL into Book page or EPUB URL. Nooky can discover explicit EPUB links on pages from sources such as Project Gutenberg and Standard Ebooks.
  2. Select Send to NOOK. Nooky downloads and validates the EPUB, adds it to the durable queue, clears the form, and shows the book in the list below.
  3. If the NOOK is asleep or offline, leave the book queued. The worker retries automatically every 15 seconds; no browser tab needs to remain open.
  4. Watch the book move through Waiting, Sending, and Delivered. After transfer and verification, Nooky asks Android to scan the new file so it appears in the reader's library.
  5. To send an EPUB already on your device, use Choose EPUB file and Upload instead of pasting a URL.

NOOK prerequisites

The reader must be prepared before Nooky can deliver books:

  • Complete Phase 4 of The Phoenix Project on a NOOK Simple Touch or Simple Touch with GlowLight.
  • Install and configure an SSH server APK on the NOOK. It must start while the reader is awake, accept key-based authentication for NOOK_USER (the default is root), and allow that user to write to NOOK_BOOK_DIR.
  • Keep the NOOK and Nooky host reachable on the same trusted LAN, with a stable hostname or reserved IP address.

Nooky does not root the reader or install/configure the SSH APK. It assumes those device-side steps are already complete.

Local development

Python 3.9 or newer and an OpenSSH client are required.

python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements-dev.txt
pytest
flask --app app.web run --debug

The app reads configuration from the environment. Set NOOK_HOST to your reader's LAN hostname or address:

Variable Default
NOOK_HOST nook.local
NOOK_PORT 22
NOOK_USER root
NOOK_BOOK_DIR /sdcard/My Files/Books
SSH_KEY_PATH /run/secrets/nook_ssh_key
SSH_KNOWN_HOSTS_PATH /run/secrets/nook_known_hosts
MAX_EPUB_SIZE_MB 100
DOWNLOAD_TIMEOUT_SECONDS 60
QUEUE_DB_PATH /data/nooky.db
SPOOL_DIR /data/books
QUEUE_POLL_SECONDS 15
NOOK_REFRESH_LIBRARY true

Pin the NOOK host key

Do this from a trusted machine on the same LAN. First collect only the RSA key:

ssh-keyscan -T 10 -t rsa nook.local > nook_known_hosts
ssh-keygen -lf nook_known_hosts

Do not trust the scan by itself. Compare the displayed SHA256 fingerprint with the fingerprint obtained from the NOOK itself or with a previously trusted connection. If the old device cannot display it directly, make the first connection while physically controlling the device and verify that the key is stable through a second independent LAN path. Nooky deliberately uses StrictHostKeyChecking=yes; a changed key stops delivery.

For a non-default SSH port, the known-hosts entry must use [host]:port. You can generate that form with ssh-keyscan -p PORT HOST.

Create Docker Swarm secrets

The commands below send the files to the Swarm manager. Keep both source files outside the repository and protect the private key locally.

docker secret create nook_ssh_key ~/.ssh/nook_rsa
docker secret create nook_known_hosts ./nook_known_hosts

Swarm mounts secrets as read-only mode 0444. The container entrypoint copies the private key—not the known-hosts file—to private temporary storage with mode 0600, because OpenSSH refuses a group/world-readable private key. It vanishes with the container.

Build and deploy

  1. Change the image name and Traefik host rule in docker-stack.yml.
  2. Ensure the external traefik-public overlay network exists and Traefik can route to it.
  3. Build and push from a machine that can publish to your registry.
  4. Deploy on a Swarm manager.
docker build -t ghcr.io/your-user/nooky:latest .
docker push ghcr.io/your-user/nooky:latest
docker stack deploy -c docker-stack.yml nooky

Keep the site LAN-only (or behind authenticated VPN/reverse-proxy access). Traefik exposes only the web service. Do not publish port 22 and do not forward the NOOK's SSH port through your router. If Traefik uses a different network or certificate resolver, adjust the labels to match your homelab.

Standalone Docker Compose

For a standalone Docker host using an external proxy network, use docker-compose.yml. It defaults to the placeholder nooky.example.com and Traefik's cloudflare certificate resolver. Set NOOKY_HOSTNAME to your own internal hostname before deployment.

Create a protected project directory on the host and place the two secret files under its secrets directory:

mkdir -p /opt/stacks/nooky/secrets
chmod 700 /opt/stacks/nooky/secrets
cp ~/.ssh/nook_rsa /opt/stacks/nooky/secrets/nook_ssh_key
cp ./nook_known_hosts /opt/stacks/nooky/secrets/nook_known_hosts
chmod 600 /opt/stacks/nooky/secrets/*
cd /opt/stacks/nooky
docker compose up -d --build

The container starts briefly as root only to copy the read-only key and host-pin secrets to mode 0600, then permanently drops to the unprivileged nooky user before starting the web application.

iPhone Share Sheet shortcut

Create an iOS Shortcut that accepts URLs from the Share Sheet and opens:

https://nooky.home.example/send?url=<URL-ENCODED-SHARED-URL>

Nooky always shows a confirmation page before fetching a URL through this route. In Safari, use Share → Add to Home Screen for an app-like launcher.

Endpoints

  • GET / — delivery form
  • POST /send-url — discover/fetch, validate, transfer, and verify
  • POST /upload — manual fallback
  • GET /send?url=… — Share Sheet confirmation
  • GET /status — NOOK reachability and free space
  • GET /jobs/<id> — queued delivery status
  • GET /health — container liveness

Security notes

Nooky rejects URL credentials and all schemes except HTTP(S). It rejects a hostname if any DNS result is loopback, private, link-local, multicast, reserved, unspecified, or otherwise non-global. The validated IP is used for the actual socket while TLS still verifies the requested hostname, preventing a DNS-rebinding gap between validation and connection. Redirects receive the same checks. The NOOK address is used only by the fixed SSH integration and cannot be selected by a submitted URL.

The app is intentionally unauthenticated because it is designed for a trusted LAN. Add authentication at Traefik or VPN level before making it reachable from any untrusted network.

About

Self-hosted EPUB delivery queue for rooted NOOK readers

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages