This guide covers local development setup, database configuration, and production deployment to Vercel with an optional Azure Functions API proxy.
Local development
1. Clone and install
git clone https://github.com/JuanNephrota/UrNammu-community.git
cd UrNammu-community
npm install Node.js 22 or later is required (the app declares engines.node >= 22).
2. Start PostgreSQL
On macOS with Homebrew:
brew install postgresql@16
brew services start postgresql@16
createdb urnammu 3. Configure environment
cp .env.example .env Set at minimum:
DATABASE_URL=postgresql://localhost:5432/urnammu
NEXTAUTH_SECRET=your-random-secret
NEXTAUTH_URL=http://localhost:3000
SETTINGS_ENCRYPTION_KEY=$(openssl rand -base64 32) # encrypts API keys stored in Settings
ENABLE_DEV_LOGIN=true 4. Set up the database
npx prisma migrate dev # Apply all migrations (also seeds the framework control catalog and built-in rules)
npm run db:seed # Load demo data 5. Start the dev server
npm run dev
Open http://localhost:3000 and sign in with the dev login
using admin@example.com.
Production deployment (Vercel)
1. Connect your repo
Link your GitHub repository to Vercel. The default build settings work out of the box.
The ai-proxy/ directory is excluded from the Next.js build via tsconfig.json.
2. Set environment variables
In Vercel project settings, configure:
DATABASE_URL=postgresql://... # Any managed Postgres
NEXTAUTH_SECRET=your-production-secret
NEXTAUTH_URL=https://your-app.vercel.app
SETTINGS_ENCRYPTION_KEY=... # Long random secret; rotating it invalidates stored credentials
GOOGLE_CLIENT_ID=... # Google OAuth (or MICROSOFT_* for Entra ID)
GOOGLE_CLIENT_SECRET=...
CRON_SECRET=... # Protects every /api/cron/* route
PROMPT_HASH_SALT=... # Must match the Azure proxy; falls back to NEXTAUTH_SECRET
ENABLE_DEV_LOGIN=false # Must be false in production 3. Migrate, then deploy
Apply migrations before the new build goes live. Merging to a Vercel-connected branch deploys the app but does not run migrations.
npx prisma migrate deploy
vercel --prod --yes 4. Scheduled jobs
vercel.json ships one cron entry per background job — one per telemetry
provider (/api/cron/provider-sync/<provider>), one per shadow AI source
(/api/cron/discovery-scan/<source>), one per directory source
(/api/cron/directory-sync/<source>), plus
governance-automation, key-usage-rules,
run-report-schedules, proxy-health, sensitive-scan,
provider-security-scan, and the three prune jobs. They are staggered so the
hourly ones do not all fire at minute zero, and each one checks its own enable flag and
interval before doing work.
Vercel's Hobby tier caps both cron frequency and the number of entries, well below what ships here. Either use Pro, or drive the routes from any external scheduler:
curl -H "Authorization: Bearer $CRON_SECRET" \
https://your-app.vercel.app/api/cron/provider-sync/anthropic The response reports whether the job was due and, if not, why and when it next will be. Skipping the prune jobs is the one omission that degrades over time rather than immediately.
API proxy (Azure Functions)
The API proxy sits in front of Claude, OpenAI, Azure OpenAI, Gemini, and Amazon Bedrock API calls, logs usage to PostgreSQL, enforces policy-as-code rules and per-agent MCP allowlists, runs dangerous-prompt and inline DLP detection, and supports streaming (SSE) for long-running requests.
Setup
cd ai-proxy
npm install
cp .env.example .env # Configure DATABASE_URL and PROXY_SECRET
Set PROMPT_HASH_SALT to the same value the app uses, otherwise the salted
prompt hashes written by the proxy will not line up with the ones from Claude Code and
Cursor telemetry and dangerous-prompt alerts will not fold across surfaces. The proxy
reads the prompt_hash_salt setting from the shared database first, then this
variable, then NEXTAUTH_SECRET. Azure OpenAI additionally needs
AZURE_OPENAI_ENDPOINT and the AZURE_OPENAI_DEPLOYMENTS
deployment→model map (or their azure_openai_endpoint /
azure_openai_deployments settings, which take precedence) before requests are
accepted.
Deploy to Azure
func azure functionapp publish your-function-app --build remote
Deploy order for an upgrade is migrations, then the app, then the proxy.
The proxy has a 10-minute function timeout configured for long-running streaming requests.
A Vercel-based fallback proxy also exists at /api/proxy/.
Pointing clients at the proxy
Every route is path-based, so the SDK appends its own paths — set the base URL and
leave everything else alone. The same paths exist on the Vercel fallback under
/api/proxy/.... Clients authenticate with x-proxy-key, and the
provider credential header is forwarded verbatim.
/api/proxy/anthropic— Claude Messages API, streaming and non-streaming (x-api-key)/api/proxy/openai/v1— Every OpenAI/v1path. Chat Completions, Completions, Responses, and Embeddings are logged with tokens and cost; other endpoints pass through as a zero-token row with the endpoint in metadata/api/proxy/azure-openai— The same endpoints on your Azure resource (api-keyor Entra bearer). Requests name a deployment, which prices through the deployment→model map/api/proxy/gemini—generateContentandstreamGenerateContent(x-goog-api-key)/api/proxy/bedrock—invokeandinvoke-with-response-streamfor Anthropic models. Log-only: the proxy forwards the client's own Bedrock API key or SigV4 signature and never injects AWS credentials
Send x-ai-system-id (and x-agent-id for agents) on each call so
usage, policy decisions, and MCP tool activity are attributed to the right registry entry;
x-user-email and x-department attribute per person and cost
center. Policy-as-code, MCP allowlists, prompt-risk detection, and response DLP apply
identically on all five upstreams. The headers, base URLs, and ready-to-paste snippets are
documented in Settings → Proxy Setup.
Upgrading from a release before the proxy-coverage change means redeploying the function
app and re-pointing OpenAI clients at /api/proxy/openai/v1: the single
chat-completions route was replaced by the path-based one, and the Azure OpenAI, Gemini,
and Bedrock routes are new.
Database management
# Create a new migration after schema changes
npx prisma migrate dev --name describe-your-change
# Regenerate the Prisma client
npx prisma generate
# Reset database (destructive!)
npm run db:reset