Plan 1: Single Demo Instance — Implementation Plan

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 Map

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 ...]

Task 1: Create demo.env

Files:

# 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"

Task 2: Modify config.edn — env var overrides

Files:

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"

Task 3: Create install-prereqs.sh

Files:

This 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"

Task 4: Create npm-setup.sh

Files:

This 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"

Task 5: Create run-demo.sh

Files:

This 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"

Task 6: Run install-prereqs.sh

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)


Task 7: Store secrets in pass

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)


Task 8: Set up DNS records

Manual step — create wildcard DNS records in Route53.

In Route53, create a CNAME (or A) record:

Same for the Plan 2 wildcard:

Run: dig +short app.demo.getorcha.com Expected: your home public IP


Task 9: Run npm-setup.sh

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


Task 10: Run the demo and verify end-to-end

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"

Task 11: Configure firewall

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