Files
InkNote/docker-compose.yml
T
marcandClaude Opus 5 53965281b4 Store imported PDFs as encrypted, deduplicated attachments
Stage two of PDF support: the file can now get in and out. No viewer
yet — that is next.

A PDF is an attachment like any other, so it inherits everything already
built: encryption at rest, naming by content hash, deduplication, the
orphan sweep, and the authenticated retrieval route the client already
caches offline. The note points at it through notes.pdf_attachment_id.

Raw request body rather than the base64 JSON images travel in. A scanned
book is tens of megabytes and base64 adds a third to that, on a path that
carries nothing but the file. The bytes are checked for %PDF- before
anything is written, so the declared content type is never trusted.

Images and PDFs are limited separately — INKNOTE_MAX_IMAGE_MB (25) and
INKNOTE_MAX_PDF_MB (200) — because a 25 MB picture is nearly always a
mistake and a 25 MB document is not. Both configurable; the limits exist
so a runaway upload cannot fill the disk, not to ration anything. The
JSON body limit now follows the image limit instead of being a second
number that could drift away from it.

Verified against a real PDF: uploaded, stored with the INKENC01 header,
returned byte-identical, deduplicated on a second upload, a GIF renamed
to .pdf refused with 415, unauthenticated refused with 401.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 20:06:34 +02:00

59 lines
2.6 KiB
YAML

# ─────────────────────────────────────────────
# InkNote — deploy from the Gitea container registry
#
# mkdir -p /srv/inknote/{db,uploads}
# chown -R 1000:1000 /srv/inknote
# cp .env.example .env && edit it # secrets + first admin
# docker login gitea.honeyy.duckdns.org
# docker compose pull && docker compose up -d
#
# Data lives under INKNOTE_DATA_DIR (default /srv/inknote) on the host, so
# whatever backs up the host also backs up InkNote. Back that path up together
# with the .env: the notes are encrypted and unreadable without ENCRYPTION_KEY.
# ─────────────────────────────────────────────
services:
inknote-server:
image: gitea.honeyy.duckdns.org/docker/inknote-server:latest
container_name: inknote-server
restart: unless-stopped
environment:
- PORT=4000
- JWT_SECRET=${JWT_SECRET}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
# Settles who the first administrator is — on an empty database by
# creating them, on an upgraded one by choosing whom to promote. Ignored
# entirely once an admin exists, so they can never reset anything.
- INKNOTE_ADMIN_USER=${INKNOTE_ADMIN_USER:-}
- INKNOTE_ADMIN_PASSWORD=${INKNOTE_ADMIN_PASSWORD:-}
- NODE_ENV=production
# Optional tuning; unset means the server's own defaults apply.
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-}
- INKNOTE_MAX_IMAGE_MB=${INKNOTE_MAX_IMAGE_MB:-}
- INKNOTE_MAX_PDF_MB=${INKNOTE_MAX_PDF_MB:-}
- MAX_VERSIONS_PER_NOTE=${MAX_VERSIONS_PER_NOTE:-}
- VERSION_RETENTION_DAYS=${VERSION_RETENTION_DAYS:-}
- VERSION_COALESCE_MINUTES=${VERSION_COALESCE_MINUTES:-}
user: "${INKNOTE_UID:-1000}:${INKNOTE_GID:-1000}"
# Bind mounts, so the data is a plain directory on the host.
# ${VAR:-fallback} means "VAR, or fallback if VAR is empty" — one or the
# other, never both. Set INKNOTE_DATA_DIR=/root/inknote and the paths below
# are /root/inknote/db and /root/inknote/uploads; /srv is then never used.
volumes:
- ${INKNOTE_DATA_DIR:-/srv/inknote}/db:/app/data
- ${INKNOTE_DATA_DIR:-/srv/inknote}/uploads:/app/uploads
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:4000/api/health"]
interval: 30s
timeout: 5s
retries: 3
inknote-web:
image: gitea.honeyy.duckdns.org/docker/inknote-web:latest
container_name: inknote-web
restart: unless-stopped
ports:
- "3000:80"
depends_on:
inknote-server:
condition: service_healthy