ServiceAgent

Two values, then you are running

Everything else this app needs — your Jobber connection, your model API key, photo retention, email — is configured in Settings once you are signed in. These two have to be environment variables, because neither can bootstrap from the database it protects.

1

A PostgreSQL database

Create a free database at neon.com (or Supabase), US East. Copy the connection string into your Hostinger environment as DATABASE_URL.

DATABASE_URL=postgres://user:pass@host/db?sslmode=require

It has to be PostgreSQL, not MySQL. Row-level security is what keeps one company's customers off another company's screen at the database level, and holds even if the app has a bug. MySQL has no equivalent — the protection would have to be app code someone promises is correct.

not set
2

An encryption key for your settings

Generate one and set it as SETTINGS_ENC_KEY. Every credential you later type into Settings is encrypted at rest with it.

openssl rand -base64 48

Back this up

Changing or losing this key makes every stored credential unreadable. They can be re-entered, but nothing will decrypt them.
not set
3

Run the migrations

In GitHub, add repository secrets ADMIN_DATABASE_URL (your database owner connection string) and APP_DB_PASSWORD (any 16+ character password). Then run the Migrate database workflow.

The admin connection string lives in GitHub secrets and never in this app's environment, so a compromised app cannot drop a table or disable a policy. The workflow also creates the restricted role the app connects as, and refuses to report success unless that role genuinely cannot bypass row-level security.

Or run it locally:

ADMIN_DATABASE_URL=... npm run migrate
ADMIN_DATABASE_URL=... APP_DB_PASSWORD=... npm run provision-role
none applied
4

Check tenant isolation is actually enforced

Waiting on the database.

Once all four are green this page hands you straight to sign-in. You can re-check any time at /api/health.