Security:
- Graceful shutdown: http.Server + signal.NotifyContext, 10s drain,
retention sweeper cancelled before pool close
- Scoped API keys (migration 00006): ingest/read/write/admin enforced in
auth middleware; scope picker + badges in Settings UI; scopeless legacy
keys act as admin. Read-only keys now get 403 on ingest (verified live)
- nginx: baseline security headers on all responses, CSP scoped to SPA;
fixed add_header inheritance shadowing
- better-auth rate limit enabled unconditionally (was gated on NODE_ENV,
which the shipped .env sets to development)
- Mailpit moved to docker-compose.dev.yml; prod compose cannot expose it
- setup.sh generates PRIMORA_ENCRYPTION_KEY
Reliability/ops:
- Retention: per-project retention_{events,audit,webhook}_days
(migration 00007, 0=off) + hourly RetentionSweeper; edit UI in Projects
- GET /api/v1/metrics Prometheus exposition
- compose: healthchecks + restart policies everywhere, dragonfly pinned
to v2.0.0, depends_on service_healthy
- backup.sh/restore.sh (pg_dump -Fc + storage volume); restore verified
against scratch postgres+volume
- .dockerignore: exclude target dirs (build context was 4.7GB)
Correctness/testing:
- DBX transfer endpoint: POST /db-connections/:id/transfer — SQL or Redis
target, 1000-row bound, server-side creds (verified live pg→dragonfly)
- Pagination audit: ListComponents capped at 500 (auto-registers)
- CI: goose up/reset/up job + compose e2e-smoke job (smoke-e2e.sh
verified locally: signup, bootstrap, scoped keys, ingest, metrics)
- k6 load sanity script: 6650 reqs, ~217/s, 0 real failures, p95 221ms
(429s = configured rate limiter working)
Docs: DEPLOYMENT_GUIDE.md, PRODUCTION_READINESS.md; verify script
repaired (stale refs + set -e arithmetic abort). ROADMAP: phase 2 +
phase 6 marked complete.
Authored By: TDvorak <[email protected]>
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Primora
A self-hosted backend platform for building products — auth, Postgres-backed APIs, object storage, JSON document collections, API keys, and a full audit log behind one dashboard. Inspired by the UX of Appwrite and Supabase, with no hosted tier: your data stays on your hardware.
Browser ──▶ Nginx ──▶ Frontend (SolidJS + Vite)
├──▶ Auth service (Better Auth + Hono)
└──▶ API (Go + Gin) ──▶ PostgreSQL
──▶ DragonflyDB (Redis-compatible cache)
──▶ Local filesystem object storage
What you get
- Organizations & projects — top-level workspaces containing projects, members, and scoped roles (
owner/admin/memberorg roles,admin/developer/viewerproject roles). - Auth — email/password plus optional GitHub, Google, Discord, and Microsoft OAuth via Better Auth. JWTs minted by the auth service are verified by the Go API against JWKS.
- Storage — S3-style buckets and objects backed by your local filesystem, with public/private visibility and downloadable URLs.
- Collections — schema-flexible JSON documents stored in Postgres JSONB.
- API keys —
prm_<prefix>_<secret>credentials; secrets are shown once. - Audit log — every mutating request recorded with actor, resource, request ID, and timestamp; CSV/JSON export from the dashboard.
- Generated client — the TypeScript client is generated from
apps/backend/openapi/openapi.yaml, so the API contract is the source of truth. - Demo mode — a fully client-side workspace (
?demo=trueorVITE_DEMO_MODE=true) for trying the UI without a backend.
Quick start (Docker)
Prerequisites: Docker with the Compose plugin.
./scripts/setup.sh # creates .env, generates secrets, starts the stack
or manually:
cp .env.example .env # fill JWT_SECRET and BETTER_AUTH_SECRET
docker compose up -d --build
Then open http://localhost. Mailpit is at http://localhost/mailpit/ for local email.
If ports 5432 or 6379 are already in use on your machine, set POSTGRES_PORT and DRAGONFLY_PORT in .env — only the host bindings change.
Verification
| Check | Endpoint |
|---|---|
| Frontend | http://localhost/ |
| Backend liveness | http://localhost/api/v1/health/liveness |
| Backend readiness | http://localhost/api/v1/health/readiness |
| Auth health | http://localhost/auth-health |
| OpenAPI spec | http://localhost/api/v1/openapi.yaml |
Local development
docker compose up -d postgres dragonfly mailpit # infrastructure only
cd apps/backend && go run ./cmd/server # API on :8080
npm run dev:auth # auth on :3001
npm run dev:frontend # dashboard (Vite)
Point the frontend at the stack by adding to apps/frontend/.env.local:
VITE_API_BASE_URL=http://localhost/api/v1
VITE_AUTH_BASE_URL=http://localhost/auth
Or skip all of it: http://localhost:<vite-port>/?demo=true runs entirely in the browser.
CLI
npm run build --workspace @primora/cli # → apps/cli/dist/cli.js
node apps/cli/dist/cli.js login # or: npx primora login once published
Supports session sign-in (primora login) and API-key mode (primora login --api-key prm_… or PRIMORA_API_KEY). Context lives in ~/.config/primora/config.json; primora use picks org + project interactively. Commands: orgs, projects, buckets, objects (list/upload/download/rm), keys, audit list --follow. Every command accepts --json.
Desktop & phone
- Desktop —
apps/desktopis a Tauri 2 shell (Linux/Windows/macOS). First run asks for your deployment URL, then renders the same dashboard in a webview with a system-tray presence. Nothing is bundled; your server does the work. See apps/desktop/README.md. - Phone / PWA — the dashboard ships a web manifest and service worker. Open your deployment in a mobile browser and "Add to Home Screen"; the shell is precached, API traffic always goes to the network. Chromium desktop browsers get an in-app install banner.
Quality gate
npm run check # backend tests + frontend typecheck + build + generated-code drift check
npm test # workspace tests
npm run generate:sqlc # regenerate sqlc after changing queries/schema
npm run generate:client # regenerate the TS client after changing openapi.yaml
Repository layout
apps/
backend/ Go + Gin API, sqlc, migrations, object storage
auth/ Better Auth on Hono (sessions, JWT, OAuth, mail)
frontend/ SolidJS + Tailwind dashboard
cli/ `primora` CLI — login, context, projects, buckets, objects, keys, audit
mcp/ `@primora/mcp` — MCP stdio server exposing `primora_*` tools
desktop/ Tauri 2 shell — connects to a deployment URL, tray presence
packages/
api-client/ OpenAPI-generated TypeScript client (do not hand-edit)
shared-types/
infra/nginx/ Reverse proxy config
scripts/ setup.sh, verify-production-ready.sh
Documentation
- QUICK_START.md — setup, configuration, troubleshooting
- project_backend.md / project_frontend.md — original design specs
- CONTRIBUTING.md — development workflow
apps/backend/openapi/openapi.yaml— API contract