For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Run a single orcha instance on Tolaria accessible at *.demo.getorcha.com, with automated NPM proxy setup and secure PDF serving via sub_filter.
Architecture: Host JVM runs orcha (3 HTTP servers on 8888/7777/9999). PostgreSQL + LocalStack run in Docker via existing docker-compose.yml. NPM provides TLS, basic auth, and rewrites presigned S3 URLs to a restricted /_s3/ proxy path. LocalStack is never exposed to the internet.
Tech Stack: Bash, Docker Compose, Java 21 (Corretto), Clojure CLI, Babashka, NPM REST API, pass for secrets
Spec: spikes/tolaria/docs/2026-03-19-plan1-single-instance-design.md
| File | Action | Purpose |
|---|---|---|
spikes/tolaria/config/demo.env |
Create | Environment variables for demo instance |
spikes/tolaria/scripts/install-prereqs.sh |
Create | Install Docker, Java 21, Clojure CLI, Babashka |
spikes/tolaria/scripts/npm-setup.sh |
Create | Create/teardown NPM proxy hosts via API |
spikes/tolaria/scripts/run-demo.sh |
Create | Start infra, build uberjar, run orcha |
orcha/resources/com/getorcha/config.edn |
Modify (lines 18-24) | Wrap app-base-url and link-base-url with #or [#env ...] |
Files:
Create: spikes/tolaria/config/demo.env
Step 1: Write demo.env
# Environment variables for Tolaria demo instance
# Source this before running orcha or init-aws
ENVIRON_NAME=local-dev
ORCHA_APP_BASE_URL=https://app.demo.getorcha.com
ORCHA_LINK_BASE_URL=https://link.demo.getorcha.com
Run: cat spikes/tolaria/config/demo.env
Expected: three env var assignments, no secrets
git add spikes/tolaria/config/demo.env
git commit -m "feat(tolaria): add demo environment variables"
Files:
Modify: orcha/resources/com/getorcha/config.edn (lines 18-24)
Step 1: Read current config.edn to confirm exact content
Run: sed -n '17,25p' orcha/resources/com/getorcha/config.edn
Expected: :com.getorcha/app-base-url and :com.getorcha/link-base-url with #profile maps
Change lines 18-20 from:
:com.getorcha/app-base-url
#profile {:local-dev "https://orcha.barreto.tech"
:default "https://app.getorcha.com"}
To:
:com.getorcha/app-base-url
#or [#env ORCHA_APP_BASE_URL
#profile {:local-dev "https://orcha.barreto.tech"
:default "https://app.getorcha.com"}]
Change lines 22-24 from:
:com.getorcha/link-base-url
#profile {:local-dev "https://link.orcha.barreto.tech"
:default "https://link.getorcha.com"}
To:
:com.getorcha/link-base-url
#or [#env ORCHA_LINK_BASE_URL
#profile {:local-dev "https://link.orcha.barreto.tech"
:default "https://link.getorcha.com"}]
Run: cd orcha && clojure -M -e '(require (quote aero.core))' 2>&1 | head -5
Expected: no errors (or clojure not installed yet — skip if prereqs not installed)
Confirm that without ORCHA_APP_BASE_URL set, the value resolves to the #profile default. This can be tested after prereqs are installed by running:
cd orcha && clojure -M:dev -e '
(require (quote [aero.core :as aero]))
(let [cfg (aero/read-config (clojure.java.io/resource "com/getorcha/config.edn") {:profile :local-dev})]
(println "app-base-url:" (:com.getorcha/app-base-url cfg))
(println "link-base-url:" (:com.getorcha/link-base-url cfg)))'
Expected: original URLs (https://orcha.barreto.tech, https://link.orcha.barreto.tech)
git add orcha/resources/com/getorcha/config.edn
git commit -m "feat(config): allow env var override for app-base-url and link-base-url"
Files:
spikes/tolaria/scripts/install-prereqs.shThis script installs Docker, Java 21, Clojure CLI, and Babashka on Tolaria (Debian Trixie aarch64). It's idempotent — skips already-installed packages.
#!/usr/bin/env bash
set -euo pipefail
# Install prerequisites for running orcha on Tolaria (Debian Trixie aarch64)
# Idempotent — safe to run multiple times.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "=== Tolaria prereqs installer ==="
# ── Docker ──
if command -v docker &>/dev/null; then
echo "[ok] Docker already installed: $(docker --version)"
else
echo "[install] Docker..."
sudo apt-get update
sudo apt-get install -y docker.io docker-compose-plugin jq
sudo usermod -aG docker "$USER"
echo "[ok] Docker installed. NOTE: log out and back in for group membership to take effect."
fi
# ── Java 21 (Amazon Corretto) ──
if java -version 2>&1 | grep -q '"21'; then
echo "[ok] Java 21 already installed: $(java -version 2>&1 | head -1)"
else
echo "[install] Java 21..."
# Use Debian's OpenJDK 21 (reliable on aarch64; Corretto apt repo may not support arm64)
sudo apt-get update
sudo apt-get install -y openjdk-21-jdk
echo "[ok] Java 21 installed: $(java -version 2>&1 | head -1)"
fi
# ── Clojure CLI ──
if command -v clojure &>/dev/null; then
echo "[ok] Clojure CLI already installed: $(clojure --version 2>&1)"
else
echo "[install] Clojure CLI..."
curl -fsSL https://download.clojure.org/install/linux-install.sh | sudo bash
echo "[ok] Clojure CLI installed: $(clojure --version 2>&1)"
fi
# ── Babashka ──
if command -v bb &>/dev/null; then
echo "[ok] Babashka already installed: $(bb --version)"
else
echo "[install] Babashka..."
curl -fsSL https://raw.githubusercontent.com/babashka/babashka/master/install | sudo bash
echo "[ok] Babashka installed: $(bb --version)"
fi
echo ""
echo "=== All prerequisites installed ==="
echo "If Docker was just installed, log out and back in, then run this script again to verify."
Run: chmod +x spikes/tolaria/scripts/install-prereqs.sh
Run: head -20 spikes/tolaria/scripts/install-prereqs.sh
Expected: shebang + set -euo pipefail + header comment
git add spikes/tolaria/scripts/install-prereqs.sh
git commit -m "feat(tolaria): add prerequisite installer script"
Files:
spikes/tolaria/scripts/npm-setup.shThis script creates/tears down NPM proxy hosts via the REST API. It reads credentials from pass, creates an access list with basic auth, requests Let's Encrypt certs, and creates proxy hosts. The app proxy host includes sub_filter + /_s3/ advanced config for PDF serving.
Before writing the script, the user must store two secrets:
# NPM admin login (the email/password you use to log into NPM UI)
pass insert -m nginx-proxy-manager
# Format:
# email: admin@example.com
# password: yourpassword
# Basic auth credentials for demo proxy hosts
pass insert -m tolaria/npm-basic-auth
# Format:
# email: demo@example.com
# password: a-strong-random-password
# NPM host address (IP:port of the NPM admin UI)
pass insert tolaria/npm-host
# Example: 192.168.2.1:81
#!/usr/bin/env bash
set -euo pipefail
# Create or teardown NPM proxy hosts for the Tolaria demo.
# Usage:
# npm-setup.sh # Create demo proxy hosts
# npm-setup.sh --teardown # Remove demo proxy hosts
# npm-setup.sh --branch NAME --slot N # Create branch proxy hosts (Plan 2)
# npm-setup.sh --branch NAME --teardown # Remove branch proxy hosts (Plan 2)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
TOLARIA_IP="192.168.2.185"
# ── Parse args ──
TEARDOWN=false
BRANCH=""
SLOT=""
while [[ $# -gt 0 ]]; do
case "$1" in
--teardown) TEARDOWN=true; shift ;;
--branch) BRANCH="$2"; shift 2 ;;
--slot) SLOT="$2"; shift 2 ;;
*) echo "Unknown arg: $1"; exit 1 ;;
esac
done
# ── Load secrets from pass ──
NPM_HOST="$(pass show tolaria/npm-host)"
NPM_EMAIL="$(pass show nginx-proxy-manager | grep 'email:' | sed 's/email: *//')"
NPM_PASS="$(pass show nginx-proxy-manager | grep 'password:' | sed 's/password: *//')"
BASIC_USER="$(pass show tolaria/npm-basic-auth | grep 'email:' | sed 's/email: *//')"
BASIC_PASS="$(pass show tolaria/npm-basic-auth | grep 'password:' | sed 's/password: *//')"
LE_EMAIL="$NPM_EMAIL"
NPM_API="http://${NPM_HOST}/api"
# ── Determine domains and ports ──
if [[ -z "$BRANCH" ]]; then
# Demo mode (Plan 1)
APP_DOMAIN="app.demo.getorcha.com"
ADMIN_DOMAIN="admin.demo.getorcha.com"
LINK_DOMAIN="link.demo.getorcha.com"
APP_PORT=8888
ADMIN_PORT=7777
LINK_PORT=9999
LOCALSTACK_PORT=4566
LABEL="demo"
else
# Branch mode (Plan 2)
if [[ -z "$SLOT" && "$TEARDOWN" == "false" ]]; then
echo "Error: --slot required with --branch for creation"
exit 1
fi
APP_DOMAIN="app-${BRANCH}.tdev.getorcha.com"
ADMIN_DOMAIN="admin-${BRANCH}.tdev.getorcha.com"
LINK_DOMAIN="link-${BRANCH}.tdev.getorcha.com"
APP_PORT=$((18000 + SLOT))
ADMIN_PORT=$((17000 + SLOT))
LINK_PORT=$((19000 + SLOT))
LOCALSTACK_PORT=$((14000 + SLOT))
LABEL="branch-${BRANCH}"
fi
# ── Authenticate ──
echo "Authenticating with NPM at ${NPM_HOST}..."
TOKEN=$(curl -sf -X POST "${NPM_API}/tokens" \
-H 'Content-Type: application/json' \
-d "{\"identity\":\"${NPM_EMAIL}\",\"secret\":\"${NPM_PASS}\"}" \
| jq -r '.token')
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
echo "Error: failed to authenticate with NPM"
exit 1
fi
AUTH="Authorization: Bearer ${TOKEN}"
# ── Helper: find proxy host by domain ──
find_host_id() {
local domain="$1"
curl -sf "${NPM_API}/nginx/proxy-hosts" -H "$AUTH" \
| jq -r ".[] | select(.domain_names[] == \"${domain}\") | .id" \
| head -1
}
# ── Teardown ──
if [[ "$TEARDOWN" == "true" ]]; then
echo "Tearing down ${LABEL} proxy hosts..."
for domain in "$APP_DOMAIN" "$ADMIN_DOMAIN" "$LINK_DOMAIN"; do
host_id=$(find_host_id "$domain")
if [[ -n "$host_id" ]]; then
curl -sf -X DELETE "${NPM_API}/nginx/proxy-hosts/${host_id}" -H "$AUTH" >/dev/null
echo " Deleted: ${domain} (id=${host_id})"
else
echo " Not found: ${domain} (skipped)"
fi
done
echo "Teardown complete."
exit 0
fi
# ── Create access list (idempotent) ──
echo "Setting up access list..."
ACL_ID=$(curl -sf "${NPM_API}/nginx/access-lists" -H "$AUTH" \
| jq -r '.[] | select(.name == "tolaria-basic-auth") | .id' | head -1)
if [[ -z "$ACL_ID" ]]; then
ACL_ID=$(curl -sf -X POST "${NPM_API}/nginx/access-lists" \
-H "$AUTH" -H 'Content-Type: application/json' \
-d "{
\"name\": \"tolaria-basic-auth\",
\"satisfy_any\": true,
\"pass_auth\": false,
\"items\": [{\"username\":\"${BASIC_USER}\",\"password\":\"${BASIC_PASS}\"}],
\"clients\": []
}" | jq -r '.id')
echo " Created access list: id=${ACL_ID}"
else
echo " Access list exists: id=${ACL_ID}"
fi
# ── NPM advanced config for app proxy host (sub_filter + /_s3/) ──
APP_ADVANCED_CONFIG="# Rewrite LocalStack presigned URLs in HTML responses
sub_filter_once off;
sub_filter_types text/html;
sub_filter 'http://localhost:${LOCALSTACK_PORT}' '/_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; }
rewrite ^/_s3/(.*)\$ /\$1 break;
proxy_pass http://127.0.0.1:${LOCALSTACK_PORT};
}"
# ── Create proxy hosts ──
create_proxy_host() {
local domain="$1"
local port="$2"
local advanced_config="${3:-}"
# Check if already exists
existing_id=$(find_host_id "$domain")
if [[ -n "$existing_id" ]]; then
echo " Exists: ${domain} (id=${existing_id}, skipped)"
return
fi
# Request Let's Encrypt cert
echo " Requesting cert for ${domain}..."
cert_id=$(curl -sf -X POST "${NPM_API}/nginx/certificates" \
-H "$AUTH" -H 'Content-Type: application/json' \
-d "{
\"provider\": \"letsencrypt\",
\"nice_name\": \"${domain}\",
\"domain_names\": [\"${domain}\"],
\"meta\": {\"dns_challenge\": false, \"letsencrypt_agree\": true, \"letsencrypt_email\": \"${LE_EMAIL}\"}
}" | jq -r '.id')
if [[ -z "$cert_id" || "$cert_id" == "null" ]]; then
echo " Error: cert creation failed for ${domain}. Check DNS propagation."
exit 1
fi
# Build proxy host JSON
local advanced_json=""
if [[ -n "$advanced_config" ]]; then
advanced_json=$(echo "$advanced_config" | jq -Rs '.')
else
advanced_json='""'
fi
echo " Creating proxy host: ${domain} -> ${TOLARIA_IP}:${port}..."
curl -sf -X POST "${NPM_API}/nginx/proxy-hosts" \
-H "$AUTH" -H 'Content-Type: application/json' \
-d "{
\"domain_names\": [\"${domain}\"],
\"forward_scheme\": \"http\",
\"forward_host\": \"${TOLARIA_IP}\",
\"forward_port\": ${port},
\"certificate_id\": ${cert_id},
\"ssl_forced\": true,
\"hsts_enabled\": true,
\"http2_support\": true,
\"block_exploits\": true,
\"allow_websocket_upgrade\": true,
\"access_list_id\": ${ACL_ID},
\"advanced_config\": ${advanced_json}
}" | jq -r '.id' >/dev/null
echo " Created: ${domain}"
}
echo "Creating ${LABEL} proxy hosts..."
create_proxy_host "$APP_DOMAIN" "$APP_PORT" "$APP_ADVANCED_CONFIG"
create_proxy_host "$ADMIN_DOMAIN" "$ADMIN_PORT"
create_proxy_host "$LINK_DOMAIN" "$LINK_PORT"
echo ""
echo "=== ${LABEL} proxy hosts created ==="
echo " App: https://${APP_DOMAIN}"
echo " Admin: https://${ADMIN_DOMAIN}"
echo " Link: https://${LINK_DOMAIN}"
Run: chmod +x spikes/tolaria/scripts/npm-setup.sh
Run: bash -n spikes/tolaria/scripts/npm-setup.sh
Expected: no syntax errors
git add spikes/tolaria/scripts/npm-setup.sh
git commit -m "feat(tolaria): add NPM proxy host automation script"
Files:
spikes/tolaria/scripts/run-demo.shThis script starts the infra (Docker), initializes LocalStack, builds the uberjar if needed, and runs orcha with the demo env vars.
#!/usr/bin/env bash
set -euo pipefail
# Start the Tolaria demo instance.
# Prerequisites: install-prereqs.sh must have been run first.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
ORCHA_DIR="${REPO_ROOT}/orcha"
ENV_FILE="${SCRIPT_DIR}/../config/demo.env"
# ── Load environment ──
if [[ ! -f "$ENV_FILE" ]]; then
echo "Error: ${ENV_FILE} not found"
exit 1
fi
echo "Loading environment from ${ENV_FILE}..."
set -a
source "$ENV_FILE"
set +a
# ── Start infrastructure ──
echo "Starting PostgreSQL + LocalStack..."
cd "$ORCHA_DIR"
docker compose up -d --wait
# ── Initialize LocalStack resources ──
echo "Initializing LocalStack resources..."
bb dev:init-aws
# ── Run database migrations ──
echo "Running database migrations..."
bb migrate migrate
# ── Build uberjar (if needed) ──
JAR_FILE="${ORCHA_DIR}/target/orcha.jar"
if [[ ! -f "$JAR_FILE" ]] || [[ -n "$(find "$ORCHA_DIR/src" "$ORCHA_DIR/resources" -newer "$JAR_FILE" 2>/dev/null | head -1)" ]]; then
echo "Building uberjar..."
clojure -X:build
else
echo "Uberjar up to date, skipping build."
fi
# ── Run orcha ──
echo ""
echo "Starting orcha (ENVIRON_NAME=${ENVIRON_NAME})..."
echo " App: http://localhost:8888 -> https://app.demo.getorcha.com"
echo " Admin: http://localhost:7777 -> https://admin.demo.getorcha.com"
echo " Link: http://localhost:9999 -> https://link.demo.getorcha.com"
echo ""
echo "Press Ctrl+C to stop."
echo ""
exec java -Xmx1g -jar "$JAR_FILE" -m com.getorcha.system
Run: chmod +x spikes/tolaria/scripts/run-demo.sh
Run: bash -n spikes/tolaria/scripts/run-demo.sh
Expected: no syntax errors
git add spikes/tolaria/scripts/run-demo.sh
git commit -m "feat(tolaria): add demo runner script"
This is the first real execution step. It installs Docker, Java 21, Clojure, and Babashka on Tolaria.
Run: spikes/tolaria/scripts/install-prereqs.sh
Expected: each package either installed or skipped with [ok]
Run: docker --version && docker compose version
Expected: Docker version and Compose version output
Run: java -version 2>&1 | head -1
Expected: openjdk version "21.x.x" (Corretto)
Run: clojure --version
Expected: Clojure CLI version
Run: bb --version
Expected: Babashka version
Docker group membership requires a new session. After re-login, verify:
Run: docker ps
Expected: empty table (no permission error)
pass insert -m nginx-proxy-manager
Enter the email and password for the NPM admin UI. Format:
email your-npm-admin@email.com
password your-npm-password
pass insert -m tolaria/npm-basic-auth
Enter username and password for demo basic auth. Format:
username demo
password a-strong-random-password
pass insert tolaria/npm-host
Enter the NPM admin address, e.g., 192.168.2.1:81
Run: pass show tolaria/npm-basic-auth
Expected: email: ... and password: ... (not an error)
Manual step — create wildcard DNS records in Route53.
In Route53, create a CNAME (or A) record:
Name: *.demo.getorcha.com
Value: your home public IP address
TTL: 300
*Step 2: Create .tdev.getorcha.com
Same for the Plan 2 wildcard:
Name: *.tdev.getorcha.com
Value: your home public IP address
TTL: 300
Step 3: Verify DNS resolution
Run: dig +short app.demo.getorcha.com
Expected: your home public IP
Verify your router forwards port 443 to Tolaria (192.168.2.185). This is needed for Let's Encrypt HTTP-01 challenges.
Run: spikes/tolaria/scripts/npm-setup.sh
Expected: authentication success, access list created, 3 certs requested, 3 proxy hosts created
Open NPM admin UI and confirm the three proxy hosts exist with SSL and access lists.
Run: curl -sI https://app.demo.getorcha.com
Expected: HTTP 401 (or 407) — basic auth required
Run: spikes/tolaria/scripts/run-demo.sh
Expected: Docker containers start, LocalStack initializes, uberjar builds (first time), orcha starts with logs showing HTTP servers on 8888/7777/9999
Open https://app.demo.getorcha.com in browser, enter basic auth credentials.
Expected: orcha app UI loads
Open https://admin.demo.getorcha.com in browser.
Expected: orcha admin UI loads
Open https://link.demo.getorcha.com in browser.
Expected: link service responds
Upload a PDF document through the app UI, then view it.
Expected: PDF renders in the iframe. Check browser DevTools — the iframe src should be https://app.demo.getorcha.com/_s3/v1-orcha-global-storage-local-stack/documents/...
Run: curl -s https://s3.tdev.getorcha.com 2>&1 | head -3
Expected: connection refused or no route (no proxy host exists for this domain)
If any adjustments were needed during testing, commit them:
git add -A spikes/tolaria/
git commit -m "fix(tolaria): adjustments from end-to-end testing"
Run: sudo apt-get install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 443/tcp # HTTPS from WAN → NPM
sudo ufw allow from 192.168.2.0/24 # Allow all LAN traffic
Run: sudo ufw enable
Expected: Firewall is active and enabled on system startup
Run: sudo ufw status
Expected: rules for SSH, 443/tcp, and LAN subnet
Open https://app.demo.getorcha.com in browser.
Expected: still accessible via HTTPS