Plan 1: Single Demo Instance on Tolaria

Date: 2026-03-19 Status: Approved

Summary

Run a single orcha instance on Tolaria (Raspberry Pi 5, 8GB RAM, Debian Trixie aarch64) accessible at *.demo.getorcha.com, with PostgreSQL and LocalStack in Docker and the orcha JVM running directly on the host. PDFs served via NPM's sub_filter rewriting + local proxy to LocalStack — no S3 endpoint exposed to the internet. NPM proxy hosts created automatically via API.

Goals

Non-Goals

Architecture

Network Topology

Browser
  │ HTTPS
  ▼
DNS (Route53)
  *.demo.getorcha.com → home IP
  *.tdev.getorcha.com → home IP  (for Plan 2 branches)
  │ :443 (port-forwarded)
  ▼
Nginx Proxy Manager (existing)
  TLS termination (Let's Encrypt)
  Basic auth on all proxy hosts
  sub_filter rewrites S3 URLs in HTML
  /_s3/ location proxies PDFs from LocalStack
  │ HTTP
  ▼
┌─────────────────────────────────┐
│ Tolaria                         │
│                                 │
│  Orcha (Host JVM, Java 21)     │
│    :8888 app                    │
│    :7777 admin                  │
│    :9999 link                   │
│                                 │
│  PostgreSQL 18 (Docker)         │
│    :5432, pgvector              │
│                                 │
│  LocalStack (Docker)            │
│    :4566, S3+SQS+SSM+KMS       │
│    NOT exposed to internet      │
└─────────────────────────────────┘

NPM Proxy Hosts

Domain Forward To SSL Basic Auth
app.demo.getorcha.com 192.168.2.185:8888 Let's Encrypt Yes
admin.demo.getorcha.com 192.168.2.185:7777 Let's Encrypt Yes
link.demo.getorcha.com 192.168.2.185:9999 Let's Encrypt Yes

Three proxy hosts only — no S3 subdomain needed.

PDF Serving via NPM (sub_filter + /_s3/ proxy)

Orcha generates presigned S3 URLs pointing to http://localhost:4566 (default local-dev behavior, unchanged). NPM rewrites these URLs in HTML responses and serves PDFs through a restricted local proxy.

NPM Advanced Configuration on the app.demo.getorcha.com proxy host:

# Rewrite LocalStack presigned URLs in HTML responses
sub_filter_once off;
sub_filter_types text/html;
sub_filter 'http://localhost:4566' '/_s3';

# Serve PDFs from LocalStack — restricted to document PDF paths only
location ~ ^/_s3/v1-orcha-global-storage-[^/]+/documents/[0-9a-f-]+(/(display|email))?\.pdf$ {
    limit_except GET { deny all; }
    proxy_pass http://127.0.0.1:4566;
}

NPM already sets proxy_set_header Accept-Encoding "" globally, which disables upstream gzip — required for sub_filter to work. No additional config needed.

Security of the /_s3/ location:

Presigned URL Flow

  1. Browser requests page from app.demo.getorcha.com (through NPM with basic auth)
  2. Orcha generates presigned S3 URL: http://localhost:4566/v1-orcha-global-storage-local-stack/documents/{uuid}.pdf?X-Amz-...
  3. NPM's sub_filter rewrites URL in HTML response to: /_s3/v1-orcha-global-storage-local-stack/documents/{uuid}.pdf?X-Amz-...
  4. Browser's iframe requests https://app.demo.getorcha.com/_s3/... (same origin — no CORS issues, basic auth cookie already present)
  5. NPM matches the /_s3/ location regex, proxies GET to localhost:4566
  6. LocalStack serves the PDF (does not validate signatures)
  7. NPM streams PDF back to browser

Key advantage: the PDF request is same-origin (app.demo.getorcha.com/_s3/...), so the browser's basic auth credentials apply automatically — no double prompt, no unauthenticated S3 endpoint.

Orcha Config Changes

Two values in config.edn wrapped with #or [#env ...] for runtime override. When env vars are unset, behavior is identical to today. No S3/endpoint changes needed.

;; App base URL (OAuth callbacks, integrations)
- :com.getorcha/app-base-url #profile {:local-dev "https://orcha.barreto.tech" ...}
+ :com.getorcha/app-base-url #or [#env ORCHA_APP_BASE_URL
+                                 #profile {:local-dev "https://orcha.barreto.tech" ...}]

;; Link base URL (OAuth issuer, discovery)
- :com.getorcha/link-base-url #profile {:local-dev "https://link.orcha.barreto.tech" ...}
+ :com.getorcha/link-base-url #or [#env ORCHA_LINK_BASE_URL
+                                  #profile {:local-dev "https://link.orcha.barreto.tech" ...}]

No changes to the S3 endpoint, aws.clj, or init_aws.clj.

Prerequisites

Package Purpose Install Method
Docker + Docker Compose PostgreSQL & LocalStack containers apt
Java 21 (Amazon Corretto) Host JVM for orcha apt (Corretto repo) or SDKMAN
Clojure CLI Build uberjar (clojure -X:build) Official installer
Babashka Task runner (bb dev:up, bb dev:init-aws) Official installer

NPM Automation

Shell script (npm-setup.sh) using NPM's REST API on port 81. The API is officially supported (OpenAPI 3.1 spec), stable (the UI consumes the same API), and well-structured.

Workflow

  1. AuthenticatePOST /api/tokens with credentials from pass show tolaria/npm-admin → JWT token
  2. Create access listPOST /api/nginx/access-lists with basic auth credentials from pass show tolaria/npm-basic-authaccess_list_id
  3. Request Let's Encrypt certsPOST /api/nginx/certificates × 3 → cert_ids
  4. Create proxy hostsPOST /api/nginx/proxy-hosts × 3, linking each to cert + access list. The app proxy host includes the sub_filter + /_s3/ advanced config.

Properties

Environment Variables

# spikes/tolaria/config/demo.env
ENVIRON_NAME=local-dev
ORCHA_APP_BASE_URL=https://app.demo.getorcha.com
ORCHA_LINK_BASE_URL=https://link.demo.getorcha.com

Two env vars only. No S3 endpoint override needed.

File Structure

spikes/tolaria/
├── docs/
│   ├── 2026-03-19-plan1-single-instance-design.md   # This file
│   └── 2026-03-19-plan2-multi-branch-design.md
├── scripts/
│   ├── install-prereqs.sh    # Install Docker, Java 21, Clojure, Babashka
│   ├── npm-setup.sh          # Create/teardown NPM proxy hosts via API
│   └── run-demo.sh           # Start infra, build, run orcha w/ env vars
├── config/
│   └── demo.env              # Environment variables for demo instance
└── systemd/                  # (Plan 2)

Startup Sequence

# One-time setup
spikes/tolaria/scripts/install-prereqs.sh
spikes/tolaria/scripts/npm-setup.sh

# Run the demo
spikes/tolaria/scripts/run-demo.sh
# Internally:
#   1. source ../spikes/tolaria/config/demo.env
#   2. cd ~/orcha/orcha && docker compose up -d
#   3. bb dev:init-aws  (uses ENVIRON_NAME=local-dev, reads :endpoint from config)
#   4. clojure -X:build  (if JAR doesn't exist or source changed)
#   5. java -Xmx1g -jar target/orcha.jar -m com.getorcha.system

Security

Deferred to Plan 2