Setup guide (manual)
An AI agent can do all of this for you (see README). These are the same steps by hand. Plan for about an hour the first time.
What you need
- Node.js 20+ and npm
- A Supabase account: https://supabase.com (the free tier is fine to start; use Pro for production backups)
- For store builds: an Expo account (https://expo.dev) and Apple/Google developer accounts
- Optional: PostgreSQL 15+ locally to run the database tests
1. Install
npm install
cp .env.example .env
2. Create the Supabase project
- In the Supabase dashboard, create a new project and choose the region closest to your team.
- Go to Project Settings → API. Copy the Project URL and the anon public key into
.env:EXPO_PUBLIC_SUPABASE_URL=https://abcd1234.supabase.co EXPO_PUBLIC_SUPABASE_ANON_KEY=eyJ...Never put theservice_rolekey in.envor anywhere in the app. - Go to Authentication → Sign In / Providers and turn off "Allow new users to sign up". Admins create accounts from inside the app.
3. Install the database
npx supabase login
npx supabase link --project-ref abcd1234 # the part before .supabase.co
npx supabase db push # applies supabase/migrations/*
This creates every table, function, policy, storage bucket and scheduled job, plus the reference data (roles, seasons, vehicle checklist, service intervals).
Optional demo data (sample areas, crops and products): open SQL Editor, paste supabase/seed.sql and run it.
4. Configure the database settings
In the SQL Editor:
update public.app_settings set value = 'America/Chicago' where key = 'timezone'; -- IANA name
update public.app_settings set value = 'United States' where key = 'national_label'; -- = countryName in brand.json
update public.app_settings set value = '1' where key = 'phone_country_code';
update public.app_settings set value = 'https://abcd1234.supabase.co/functions/v1' where key = 'functions_url';
select value from public.app_settings where key = 'webhook_secret'; -- copy this for step 6
national_label must match countryName in config/brand.json exactly. Dashboards use it to find the company-wide totals.
5. Create your first admin
- Authentication → Users → Add user: enter your email and a password, and tick "Auto confirm".
- In the SQL Editor:
sql update public.users set role = 'ADMIN', full_name = 'Your Name' where id = (select id from auth.users where email = 'you@company.com'); - Sign in to the app with that email. You create everyone else in Admin → Users.
6. Push notifications
npx supabase secrets set WEBHOOK_SECRET=<the value from step 4> APP_NAME="Your App Name"
npx supabase functions deploy send-activity-push
- Phones: delivered through Expo push. Android also needs Firebase: create a Firebase project, add an Android app with your
bundleId, downloadgoogle-services.jsonto the project root, and upload the FCM credentials in your Expo project (expo.dev → Credentials). - Web console (optional): run
npx web-push generate-vapid-keys. Put the public key in.envasEXPO_PUBLIC_VAPID_PUBLIC_KEY, and set the function secretsVAPID_PUBLIC_KEY,VAPID_PRIVATE_KEYandVAPID_SUBJECT=mailto:you@company.com.
7. Brand and localise
Edit config/brand.json (every field is described in CONFIGURATION.md) and replace the images in assets/ (icon.png 1024×1024, adaptive-icon.png, splash-icon.png, notification-icon.png white-on-transparent). Also replace public/icon-192.png, public/icon-512.png and public/apple-touch-icon.png for the web console.
8. Run it
npm run web # web console in the browser
npm start # scan the QR code with a development build or Expo Go
9. Ship
Web console:
npx expo export --platform web # static site in dist/, host on Vercel, Netlify, Cloudflare Pages...
Mobile:
npm i -g eas-cli && eas login
eas init # creates the Expo project, then put its id in brand.json -> easProjectId
eas build --platform android --profile production
eas build --platform ios --profile production
eas submit --platform all
Over-the-air updates (optional): eas update:configure, then set easUpdatesUrl in brand.json.
10. Scheduled reminders
The fleet and expense reminders run with pg_cron, on UTC schedules:
| Job | When (UTC) |
|---|---|
app_reminder_inspection_1st |
04:00 on the 1st |
app_reminder_inspection_7th |
04:00 on the 7th |
app_reminder_inspection_late |
04:00 on the 8th |
app_reminder_sheet_last_day |
12:00 on days 28–31 (the function only acts on the real last day) |
app_reminder_sheet_overdue |
04:00 on the 5th |
app_reminder_weekly_mon / _thu |
04:00 Mon / Thu |
To move one to your morning, for example 08:00 in New York (13:00 UTC):
select cron.alter_job((select jobid from cron.job where jobname = 'app_reminder_weekly_mon'), schedule := '0 13 * * 1');
Verify the database (optional)
With a local PostgreSQL 15+:
PGHOST=localhost PGUSER=postgres ./scripts/test-db.sh
It installs the schema into a temporary database and runs the functional smoke test and the security suite. The last line should read ALL SECURITY CHECKS PASSED.
Troubleshooting
| Symptom | Fix |
|---|---|
| App shows "Missing EXPO_PUBLIC_SUPABASE_URL" | .env is missing or Expo was started before you created it. Restart with npx expo start -c |
| Signed in but dashboards are empty | national_label in app_settings doesn't match countryName in brand.json, or there are no areas, targets or users yet |
| No push notifications | Check functions_url is set, WEBHOOK_SECRET matches app_settings.webhook_secret, and the function logs (Edge Functions → send-activity-push → Logs) |
| "permission denied" for a user | Check their role, province, region_label and area_name in public.users. Access follows those fields |