Sthora
Contributing

Development setup

Running the whole platform on your own machine, and the two values you have to supply.

Prerequisites

ToolVersionNotes
Node.js>=22Declared in the root engines.node.
pnpm^11Pinned via devEngines.packageManager — it downloads itself if missing.
PostgreSQL>=16Reached through DATABASE_URL. No test command needs one.
Redis>=7For BullMQ and the live metric cache. Set maxmemory-policy noeviction.
DockerAny recent, with the Compose v2 pluginRequired to deploy anything. Without it, container panels report "unreachable", deployments fail at the build step, and everything else still works.

Install

pnpm install
cp apps/backend/.env.example apps/backend/.env
cp apps/dashboard/.env.example apps/dashboard/.env

Supply the two values with no safe default in apps/backend/.env:

DATABASE_URL=postgresql://…
AUTH_SECRET=$(openssl rand -hex 32)

Every other variable ships with a working default. Only .env.example is ever committed.

To store environment variables for deployed applications, generate a master key as well and point SECRET_ENCRYPTION_KEY_FILE at it.

Create the schema

pnpm --filter @sthora/backend db:deploy

Run

AppCommandURL
APIpnpm --filter @sthora/backend devhttp://localhost:4600 — health at /health, OpenAPI at /docs
Email previewspnpm --filter @sthora/backend email:devhttp://localhost:4601
Dashboardpnpm --filter @sthora/dashboard devhttp://localhost:3600
Marketing sitepnpm --filter @sthora/website devhttp://localhost:3601
This sitepnpm --filter @sthora/docs devhttp://localhost:3602
Everything under apps/pnpm devIn parallel

Production builds:

pnpm --filter @sthora/backend build && pnpm --filter @sthora/backend start
pnpm --filter @sthora/dashboard build && pnpm --filter @sthora/dashboard start

Local development is split-origin

The dashboard runs on 3600 and the API on 4600, so the API needs both:

CORS_ALLOWED_ORIGINS=http://localhost:3600
AUTH_TRUSTED_ORIGINS=http://localhost:3600

A self-hosted install serves both on one origin and leaves the first empty.

Writable paths

By default the pipeline writes to /var/lib/sthora on the managed server and stages uploads in /var/lib/sthora/uploads on the control plane. On a development machine point DEPLOY_DATA_ROOT and DEPLOY_UPLOAD_STAGING_PATH somewhere writable instead.

Database commands

pnpm --filter @sthora/backend db:generate   # regenerate the Prisma client
pnpm --filter @sthora/backend db:migrate    # create and apply a migration
pnpm --filter @sthora/backend db:deploy     # apply committed migrations
pnpm --filter @sthora/backend db:studio     # browse the data
pnpm --filter @sthora/backend db:validate   # check the schema

prisma generate also runs on install, so a fresh clone builds without a manual step.

Prisma 7 note

The connection URL lives in apps/backend/prisma.config.ts only. Prisma 7 removed url from the schema's datasource block, and the running app passes a @prisma/adapter-pg adapter to the client instead.

Editing these docs

Pages are MDX under apps/docs/content/docs/. Adding a file adds a page — the sidebar, the search index, the sitemap, and llms.txt are all generated from the same tree. Order a folder with its meta.json.

Every page needs a title and a description in its frontmatter. A test asserts it, because a missing description is invisible until a search result renders empty.

On this page