deployment
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# Development
|
||||
|
||||
This document collects runtime assumptions and workflows for local development,
|
||||
integration testing, and infrastructure automation.
|
||||
|
||||
## Local Development
|
||||
|
||||
Use the provided `papercrate.tmux` to spin up the full stack in one tmux session:
|
||||
|
||||
```bash
|
||||
tmux -f papercrate.tmux attach
|
||||
```
|
||||
|
||||
This creates windows for the compose stack, frontend dev server, backend API, and
|
||||
background worker using the repository-relative paths defined in the tmux file.
|
||||
Detach with `Ctrl+b d` and reattach later with the same command.
|
||||
|
||||
## Backend Integration Tests
|
||||
|
||||
Integration tests require a running Postgres instance (and, optionally, Quickwit
|
||||
for OCR indexing). The repository includes a lightweight compose file for local
|
||||
runs:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.test.yml up -d
|
||||
export TEST_DATABASE_URL=postgres://papercrate:papercrate_test@localhost:5433/papercrate_test
|
||||
# optional, enables Quickwit indexing jobs
|
||||
export QUICKWIT_ENDPOINT=http://localhost:7280
|
||||
export QUICKWIT_INDEX=documents
|
||||
cargo test
|
||||
```
|
||||
|
||||
Stop the database when you are done:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.test.yml down
|
||||
```
|
||||
|
||||
The compose service uses tmpfs storage, giving each test run a clean database.
|
||||
|
||||
## Runtime Dependencies
|
||||
|
||||
- `ocrmypdf` (optional but recommended): Used by the OCR worker to extract text
|
||||
from PDFs when no embedded text layer is available. Ensure it is installed and
|
||||
available on the worker hosts if OCR is desired.
|
||||
- Quickwit (optional): The Quickwit indexer is used to ingest extracted text for
|
||||
search. Set `QUICKWIT_ENDPOINT` and `QUICKWIT_INDEX` in the environment when
|
||||
running workers if you want indexing jobs to run. The local compose file starts
|
||||
a Quickwit instance on `http://localhost:7280` and seeds the `documents` index
|
||||
automatically.
|
||||
|
||||
## Configuration
|
||||
|
||||
The backend reads its settings from environment variables. In particular:
|
||||
|
||||
- `DATABASE_URL` – connection string for the primary Postgres database (required).
|
||||
- `DATABASE_MAX_POOL_SIZE` – optional override for the r2d2 connection pool size.
|
||||
Defaults to `2`; increase it in staging/production to match expected concurrency.
|
||||
|
||||
On startup each binary logs the effective configuration with secrets redacted
|
||||
(for example, the database password is masked). This makes it easier to confirm
|
||||
runtime settings in staging without exposing credentials.
|
||||
|
||||
## Running Migrations in Kubernetes
|
||||
|
||||
The backend container image ships the `diesel` CLI, so schema migrations can be
|
||||
executed as a short-lived Job (or Helm hook) before rolling out new pods. Example
|
||||
manifest:
|
||||
|
||||
```yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: papercrate-migrate
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: migrate
|
||||
image: ghcr.io/example/papercrate-backend:<TAG>
|
||||
command: ["/usr/local/bin/diesel", "migration", "run"]
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: papercrate-db
|
||||
key: DATABASE_URL
|
||||
```
|
||||
|
||||
Run the Job manually (`kubectl apply -f migrate-job.yaml`) or configure it as a
|
||||
Helm pre-install/pre-upgrade hook so migrations run automatically on each
|
||||
deployment. Once the Job succeeds, deploy/update the backend `Deployment` as
|
||||
usual.
|
||||
@@ -1,73 +1,41 @@
|
||||
# Papercrate
|
||||
|
||||
## Local Development
|
||||

|
||||
|
||||
Use the provided `papercrate.tmux` to spin up the full stack in one tmux session:
|
||||
## Single-Host Deployment (Docker Compose)
|
||||
|
||||
For self-hosting (for example on a Raspberry Pi), use the production-oriented
|
||||
`docker-compose.yml`. Define the required secrets in a `.env` file alongside the
|
||||
compose file before starting the stack:
|
||||
|
||||
```bash
|
||||
tmux -f papercrate.tmux attach
|
||||
cat <<'EOF' > .env
|
||||
POSTGRES_PASSWORD=change-me
|
||||
MINIO_ROOT_PASSWORD=change-me-too
|
||||
JWT_SECRET=generate-a-long-random-string
|
||||
WEBAUTHN_RP_ID=papercrate.local
|
||||
WEBAUTHN_ORIGIN=http://papercrate.local:8080
|
||||
# Optional overrides
|
||||
# CORS_ALLOWED_ORIGIN=http://papercrate.local:8080
|
||||
# REFRESH_COOKIE_SECURE=true
|
||||
EOF
|
||||
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This creates windows for the compose stack, frontend dev server, backend API, and background worker using the repository-relative paths defined in the tmux file. Detach with `Ctrl+b d` and reattach later with the same command.
|
||||
**Important:** the WebAuthn settings must match the public URL clients will use.
|
||||
The relying party (RP) identifier is the bare host name, while the origin must
|
||||
include scheme and port. Adjust the values above if you serve Papercrate from a
|
||||
different host, domain, or HTTPS endpoint—otherwise passkey registration and
|
||||
login will fail.
|
||||
|
||||
## Backend Integration Tests
|
||||
The compose file builds the backend and frontend images locally, then launches
|
||||
Postgres, MinIO, Quickwit, the API, background worker, WebDAV endpoint, and the
|
||||
SPA frontend. Once the containers report healthy, visit `http://<host>:8080`
|
||||
and use the passkey signup flow to provision the first tenant/user. Upgrades are
|
||||
as simple as `git pull` followed by `docker compose up -d`.
|
||||
|
||||
Integration tests require a running Postgres instance (and, optionally, Quickwit for OCR indexing). The repository includes a lightweight compose file for local runs:
|
||||
---
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.test.yml up -d
|
||||
export TEST_DATABASE_URL=postgres://papercrate:papercrate_test@localhost:5433/papercrate_test
|
||||
# optional, enables Quickwit indexing jobs
|
||||
export QUICKWIT_ENDPOINT=http://localhost:7280
|
||||
export QUICKWIT_INDEX=documents
|
||||
cargo test
|
||||
```
|
||||
|
||||
Stop the database when you are done:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.test.yml down
|
||||
```
|
||||
|
||||
The compose service uses tmpfs storage, giving each test run a clean database.
|
||||
|
||||
## Runtime Dependencies
|
||||
|
||||
- `ocrmypdf` (optional but recommended): Used by the OCR worker to extract text from PDFs when no embedded text layer is available. Ensure it is installed and available on the worker hosts if OCR is desired.
|
||||
- Quickwit (optional): The Quickwit indexer is used to ingest extracted text for search. Set `QUICKWIT_ENDPOINT` and `QUICKWIT_INDEX` in the environment when running workers if you want indexing jobs to run. The local compose file starts a Quickwit instance on `http://localhost:7280` and seeds the `documents` index automatically.
|
||||
|
||||
## Configuration
|
||||
|
||||
The backend reads its settings from environment variables (see `backend/.env` for local defaults). In particular:
|
||||
|
||||
- `DATABASE_URL` – connection string for the primary Postgres database (required).
|
||||
- `DATABASE_MAX_POOL_SIZE` – optional override for the r2d2 connection pool size. Defaults to `2`; increase it in staging/production to match expected concurrency.
|
||||
|
||||
On startup each binary logs the effective configuration with secrets redacted (for example, the database password is masked). This makes it easier to confirm the runtime settings in staging without exposing credentials.
|
||||
|
||||
## Running Migrations in Kubernetes
|
||||
|
||||
The backend container image ships the `diesel` CLI, so schema migrations can be executed as a short-lived Job (or Helm hook) before rolling out new pods. Example manifest:
|
||||
|
||||
```yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: papercrate-migrate
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: migrate
|
||||
image: ghcr.io/example/papercrate-backend:<TAG>
|
||||
command: ["/usr/local/bin/diesel", "migration", "run"]
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: papercrate-db
|
||||
key: DATABASE_URL
|
||||
```
|
||||
|
||||
Run the Job manually (`kubectl apply -f migrate-job.yaml`) or configure it as a Helm pre-install/pre-upgrade hook so migrations run automatically on each deployment. Once the Job succeeds, deploy/update the backend `Deployment` as usual.
|
||||
For development workflows (local stack, integration tests, migrations, and
|
||||
configuration details) see [DEVELOPMENT.md](./DEVELOPMENT.md).
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
version: "3.9"
|
||||
|
||||
x-app-env: &app-env
|
||||
DATABASE_URL: postgres://papercrate:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}@postgres:5432/papercrate
|
||||
DATABASE_MAX_POOL_SIZE: ${DATABASE_MAX_POOL_SIZE:-8}
|
||||
SERVER_HOST: 0.0.0.0
|
||||
SERVER_PORT: 3000
|
||||
WEBDAV_HOST: 0.0.0.0
|
||||
WEBDAV_PORT: 3001
|
||||
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must be set}
|
||||
JWT_ISSUER: ${JWT_ISSUER:-papercrate}
|
||||
JWT_AUDIENCE: ${JWT_AUDIENCE:-papercrate-clients}
|
||||
JWT_EXPIRY_MINUTES: ${JWT_EXPIRY_MINUTES:-60}
|
||||
DOWNLOAD_TOKEN_AUDIENCE: ${DOWNLOAD_TOKEN_AUDIENCE:-papercrate-download}
|
||||
DOWNLOAD_TOKEN_EXPIRY_MINUTES: ${DOWNLOAD_TOKEN_EXPIRY_MINUTES:-60}
|
||||
REFRESH_TOKEN_EXPIRY_DAYS: ${REFRESH_TOKEN_EXPIRY_DAYS:-30}
|
||||
REFRESH_COOKIE_SECURE: ${REFRESH_COOKIE_SECURE:-false}
|
||||
REFRESH_COOKIE_DOMAIN: ${REFRESH_COOKIE_DOMAIN:-}
|
||||
CORS_ALLOWED_ORIGIN: ${CORS_ALLOWED_ORIGIN:-}
|
||||
AWS_ENDPOINT_URL: http://minio:9000
|
||||
AWS_ACCESS_KEY_ID: ${MINIO_ROOT_USER:-papercrate}
|
||||
AWS_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD must be set}
|
||||
AWS_REGION: ${AWS_REGION:-us-east-1}
|
||||
S3_BUCKET: ${S3_BUCKET:-documents}
|
||||
QUICKWIT_ENDPOINT: http://quickwit:7280
|
||||
QUICKWIT_INDEX: ${QUICKWIT_INDEX:-documents}
|
||||
WORKER_MAX_DOCUMENT_BYTES: ${WORKER_MAX_DOCUMENT_BYTES:-209715200}
|
||||
UPLOAD_BODY_LIMIT_BYTES: ${UPLOAD_BODY_LIMIT_BYTES:-134217728}
|
||||
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-papercrate.local}
|
||||
WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://papercrate.local}
|
||||
WEBAUTHN_RP_NAME: ${WEBAUTHN_RP_NAME:-Papercrate}
|
||||
RUST_LOG: ${RUST_LOG:-info}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: papercrate
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}
|
||||
POSTGRES_DB: papercrate
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U papercrate -d papercrate"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
ports:
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
command: server /data --console-address ":9001"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-papercrate}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD must be set}
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
ports:
|
||||
- "${MINIO_API_PORT:-9000}:9000"
|
||||
- "${MINIO_CONSOLE_PORT:-9001}:9001"
|
||||
|
||||
createbuckets:
|
||||
image: minio/mc:latest
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-papercrate}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD must be set}
|
||||
S3_BUCKET: ${S3_BUCKET:-documents}
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
set -e;
|
||||
/usr/bin/mc alias set papercrate http://minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD};
|
||||
/usr/bin/mc mb papercrate/$${S3_BUCKET} --ignore-existing;
|
||||
exit 0;
|
||||
"
|
||||
restart: "no"
|
||||
|
||||
quickwit:
|
||||
image: quickwit/quickwit:0.8.2
|
||||
command: ["run"]
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
QW_ENABLE_API_AUTH: "false"
|
||||
QW_DATA_DIR: /quickwit/data
|
||||
volumes:
|
||||
- quickwit_data:/quickwit/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://127.0.0.1:7280/api/v1/version"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
ports:
|
||||
- "${QUICKWIT_PORT:-7280}:7280"
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
image: papercrate/backend:latest
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
createbuckets:
|
||||
condition: service_completed_successfully
|
||||
quickwit:
|
||||
condition: service_healthy
|
||||
migrator:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
<<: *app-env
|
||||
ports:
|
||||
- "${API_PORT:-3000}:3000"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:3000/api/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
worker:
|
||||
image: papercrate/backend:latest
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
<<: *app-env
|
||||
entrypoint: ["/usr/local/bin/papercrate-worker"]
|
||||
restart: unless-stopped
|
||||
|
||||
webdav:
|
||||
image: papercrate/backend:latest
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
<<: *app-env
|
||||
entrypoint: ["/usr/local/bin/papercrate-webdav"]
|
||||
ports:
|
||||
- "${WEBDAV_PORT:-3001}:3001"
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
image: papercrate/frontend:latest
|
||||
environment:
|
||||
API_PROXY_PASS: http://backend:3000
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "${FRONTEND_PORT:-8080}:80"
|
||||
restart: unless-stopped
|
||||
|
||||
migrator:
|
||||
image: papercrate/backend:latest
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
<<: *app-env
|
||||
entrypoint: ["/usr/local/bin/diesel"]
|
||||
command: ["migration", "run"]
|
||||
restart: "no"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
minio_data:
|
||||
quickwit_data:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 798 KiB |
+1
-1
@@ -1,5 +1,5 @@
|
||||
# tmux session for Papercrate dev stack
|
||||
new-session -d -s papercrate -n docker -c . 'docker compose up'
|
||||
new-session -d -s papercrate -n docker -c . 'docker compose -f docker-composer.dev.yml up'
|
||||
|
||||
new-window -t papercrate:1 -n frontend -c ./frontend 'npm run dev'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user