Files
hackerspace.sh/docs/DEPLOYMENT.md
T
Moheeb Zara ea8d7a5f91 feat: governance kernel, deployment hardening, local-dev stack
Adds proposals + proposal_votes + policies + incidents + incident_updates
tables with full RLS, async voting flow, dismissal-appeal pathway,
versioned policies with provenance, and an anonymous-incident token mode.
Adds per-space configurable areas, financial visibility settings, member
self-profile + COI disclosure, and a mission statement that renders in
the app footer.

Reorganises lib/actions.ts into per-domain files (tasks, projects,
members, payments, contacts, knowledge-base, secrets, area-leads,
settings, imports, proposals, incidents, policies, areas) plus a barrel.
Adds lib/permissions.ts and lib/auth-helpers.ts so action bodies stop
duplicating the user-to-member-to-role check. Wires every action that has
a Zod schema; introduces a flexibleDateTime preprocessor so HTML date
pickers no longer hit "Invalid datetime".

Hardens RLS: drops NOT NULL on space_members.user_id for offline members,
adds a privilege-escalation trigger that blocks self-edits to role / tier
/ status / approved / has_card_access / space_id, closes four
cross-tenant holes in the governance kernel, and adds a partial unique
index so two admins cannot concurrently activate two drafts of the same
policy slug.

Ships every deployment artifact: Dockerfile, docker-compose,
.do/app.yaml, .env.example, /api/health, plus a managed-Supabase guide
(docs/DEPLOYMENT.md), an end-to-end self-hosted Droplet guide
(docs/DEPLOY_DO_SELFHOSTED.md), and a Supabase-CLI local-dev quickstart
(docs/LOCAL_DEV.md). Replaces middleware.ts with proxy.ts for Next.js 16.
Adds scripts/schema.sql as the canonical idempotent full deploy plus
fourteen numbered incremental migrations.

Adds scripts/seed.sql so a fresh local DB renders something instead of
empty pages: a demo space, five members across all roles, a v1 code of
conduct, three proposals in different states with seven votes already
cast, an open incident, a project, three tasks, a KB entry, and matching
activity-log rows.

Replaces v0-generated test scaffolding with 266 real tests across
__tests__/{actions,auth-helpers,components,governance,permissions,
security,utils,validations}.test.ts. Adds react-markdown + remark-gfm
for proposal / incident / policy body rendering. Deletes four dead v0
fix-* scripts, the unused components/app-shell.tsx, and the duplicate
app/auth/login/page.tsx.

Routes (31 total): /api/health, /api/paypal/sync, /auth/callback, /,
/login, /signup, /signup/confirm, /dashboard, /tasks, /projects,
/members, /payments, /financials, /contacts, /ops, /ops/[id], /ops/new,
/proposals, /proposals/[id], /proposals/new, /incidents, /incidents/[id],
/incidents/new, /policies, /policies/[slug], /policies/new, /profile,
/recruitment, /settings, /comms, /import.

Full pass-by-pass history is in docs/HANDOFF.md; the roadmap is in
docs/GOVERNANCE_FEATURES.md.
2026-05-14 04:52:11 -07:00

573 lines
16 KiB
Markdown

# Hackerspace.sh — Deployment Guide
Complete instructions for every supported deployment target that uses **managed Supabase Cloud** as the database / auth layer.
For a fully self-hosted deployment where you run Supabase yourself on a DigitalOcean Droplet (Postgres, GoTrue, PostgREST, Realtime, Storage, Studio — all on your own server), see [DEPLOY_DO_SELFHOSTED.md](./DEPLOY_DO_SELFHOSTED.md). That guide is end-to-end and assumes nothing else.
---
## Table of Contents
1. [Prerequisites](#1-prerequisites)
2. [Supabase Setup (required for all targets)](#2-supabase-setup)
3. [Environment Variables Reference](#3-environment-variables)
4. [Option A — Vercel (recommended)](#4-option-a--vercel)
5. [Option B — DigitalOcean App Platform](#5-option-b--digitalocean-app-platform)
6. [Option C — DigitalOcean Droplet (Docker / self-hosted)](#6-option-c--digitalocean-droplet)
7. [Post-Deploy Checklist](#7-post-deploy-checklist)
8. [Upgrading an Existing Deployment](#8-upgrading)
9. [Troubleshooting](#9-troubleshooting)
---
## 1. Prerequisites
| Tool | Version | Purpose |
|------|---------|---------|
| Node.js | 20+ | Local development |
| pnpm | 9+ | Package manager |
| Git | any | Source control |
| Supabase account | free tier works | Database + auth + realtime |
| Vercel / DigitalOcean account | — | Hosting |
Clone the repo:
```bash
git clone https://github.com/virgilvox/hackerspace-management.git
cd hackerspace-management
pnpm install
```
---
## 2. Supabase Setup
This step is identical regardless of which hosting option you choose.
### 2.1 Create a Supabase project
1. Go to [supabase.com](https://supabase.com) → **New project**
2. Choose a region close to your users
3. Set a strong database password and save it — you won't need it directly but keep it safe
4. Wait ~2 minutes for provisioning
### 2.2 Apply the schema
1. In the Supabase dashboard, go to **SQL Editor**
2. Click **New query**
3. Paste the entire contents of `scripts/schema.sql`
4. Click **Run** (or Ctrl+Enter)
You should see no errors. The script is idempotent — safe to re-run.
### 2.3 Configure Auth
In the Supabase dashboard:
**Authentication → Settings → General**
- Set **Site URL** to your app's public URL (e.g. `https://yourapp.vercel.app`)
- Add the same URL to **Redirect URLs**
- Also add `http://localhost:3000` for local dev
**Authentication → Settings → Email**
- Enable **Confirm email** if you want verified signups
- Customize the email templates as needed
**Authentication → Providers**
- Email/Password is enabled by default — no changes needed
- Optionally enable Google, GitHub, etc. (social login buttons are wired in the UI)
### 2.4 Enable Realtime
The schema script already runs `ALTER PUBLICATION supabase_realtime ADD TABLE ...` for `comms_messages` and `comms_channels`. Verify in:
**Database → Replication** — confirm both tables appear under `supabase_realtime`.
### 2.5 Get your API keys
**Project Settings → API**
Copy these four values — you'll need them in every deployment:
```
NEXT_PUBLIC_SUPABASE_URL = https://xxxxxxxxxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY = eyJhbGci...
SUPABASE_SERVICE_ROLE_KEY = eyJhbGci... (keep this SECRET — server-side only)
```
### 2.6 Create your first Space + Admin user
After deploying the app, navigate to `/signup`. The signup flow will:
1. Create an `auth.users` row in Supabase
2. Trigger `handle_space_signup()` which inserts a `space_members` row
To make yourself an admin, run this in the SQL editor (replace values):
```sql
UPDATE public.space_members
SET role = 'admin', status = 'current'
WHERE email = 'your@email.com';
```
---
## 3. Environment Variables
The repo ships an `.env.example` file. Copy it locally:
```bash
cp .env.example .env.local
```
Then fill in real values. For deployed environments, set the same keys in the host's dashboard.
Required variables:
| Key | Where it is used |
|-----|------------------|
| `NEXT_PUBLIC_SUPABASE_URL` | Browser + server, in every request |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Browser + server |
| `SUPABASE_SERVICE_ROLE_KEY` | Server only. Bypasses RLS. Used by createSpace / joinSpace |
| `NEXT_PUBLIC_APP_URL` | OAuth callback URLs |
| `NODE_ENV` | Runtime mode |
Per-space integration credentials (PayPal, Zeffy, Venmo, Stripe) are stored in the `integrations` table, not in env. The Settings UI writes them; `/api/paypal/sync` reads them.
`.env.local` is git-ignored. Never commit real secrets.
---
## 4. Option A — Vercel
The simplest and fastest path. Zero server management.
### 4.1 Deploy via Vercel CLI
```bash
# Install Vercel CLI globally
pnpm add -g vercel
# From the project root:
vercel
# Follow the prompts:
# - Link to existing project or create new
# - Framework: Next.js (auto-detected)
# - Root directory: ./ (default)
```
### 4.2 Set environment variables
```bash
vercel env add NEXT_PUBLIC_SUPABASE_URL production
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY production
vercel env add SUPABASE_SERVICE_ROLE_KEY production
vercel env add NEXT_PUBLIC_APP_URL production
```
Or set them in the Vercel dashboard: **Project → Settings → Environment Variables**
### 4.3 Deploy to production
```bash
vercel --prod
```
### 4.4 Connect a custom domain (optional)
Vercel dashboard → **Domains** → add your domain → follow DNS instructions.
### 4.5 Continuous deployment
Push to `main` branch → Vercel auto-deploys. Push to any other branch → preview deployment at a unique URL.
```bash
git push origin main # triggers production deploy
git push origin feat/my-feature # triggers preview deploy
```
---
## 5. Option B — DigitalOcean App Platform
Managed container hosting. No Docker knowledge required. Scales automatically.
The repo ships an App Platform spec at `.do/app.yaml`. You can either apply it directly with `doctl` or use the UI flow below.
### 5.1 Push your repo to GitHub
App Platform deploys directly from GitHub.
```bash
git remote add origin https://github.com/YOUR_ORG/hackerspace-management.git
git push -u origin main
```
### 5.2a Create the app via `doctl` (recommended)
Edit `.do/app.yaml` and replace `REPO_OWNER/REPO_NAME` and the placeholder env values. Then:
```bash
doctl apps create --spec .do/app.yaml
```
### 5.2b Or create via the UI
1. Go to [cloud.digitalocean.com/apps](https://cloud.digitalocean.com/apps)
2. Click **Create App** then **GitHub**, authorize and select your repo
3. Select branch: `main`
4. App Platform auto-detects Next.js. Confirm the build settings:
- **Build command**: `corepack enable && pnpm install --frozen-lockfile && pnpm build`
- **Run command**: `pnpm start`
- **HTTP port**: `3000`
- **Health check path**: `/api/health`
### 5.3 Set environment variables
In the App Platform UI, go to **Settings → App-Level Environment Variables** and add:
```
NEXT_PUBLIC_SUPABASE_URL = (your value)
NEXT_PUBLIC_SUPABASE_ANON_KEY = (your value)
SUPABASE_SERVICE_ROLE_KEY = (mark as Encrypted)
NEXT_PUBLIC_APP_URL = https://your-app.ondigitalocean.app
NODE_ENV = production
```
Mark `SUPABASE_SERVICE_ROLE_KEY` as **Encrypted** so it's not visible in logs.
### 5.4 Choose a plan
- **Basic** ($5/mo): fine for small hackerspaces (<50 members)
- **Professional** ($12/mo): recommended for production — includes more RAM and 99.99% SLA
### 5.5 Deploy
Click **Create Resources** → wait ~3 minutes for the first build.
### 5.6 Custom domain
App Platform dashboard → **Settings → Domains** → add domain → update your DNS with the CNAME provided.
### 5.7 Continuous deployment
App Platform auto-deploys on every push to `main` by default. You can also configure it to deploy only on tagged releases.
---
## 6. Option C — DigitalOcean Droplet
Full self-hosted on a VPS. Maximum control. Use this if you want everything on your own infrastructure.
### 6.1 Create a Droplet
1. Go to [cloud.digitalocean.com/droplets](https://cloud.digitalocean.com/droplets)
2. **Create Droplet**
- Image: **Ubuntu 24.04 LTS**
- Plan: **Basic — Regular — $12/mo** (2 vCPU, 2GB RAM) minimum; $24/mo recommended
- Datacenter: closest to your users
- Authentication: SSH key (strongly recommended over password)
3. Note the Droplet IP address
### 6.2 Initial server setup
SSH in and harden the server:
```bash
ssh root@YOUR_DROPLET_IP
# Create a non-root user
adduser deploy
usermod -aG sudo deploy
# Copy SSH key to new user
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
# Switch to deploy user
su - deploy
```
### 6.3 Install Docker and Docker Compose
```bash
# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker deploy
# Install Docker Compose plugin
sudo apt-get install -y docker-compose-plugin
# Verify
docker --version
docker compose version
```
### 6.4 Install Nginx and Certbot
```bash
sudo apt-get update
sudo apt-get install -y nginx certbot python3-certbot-nginx
```
### 6.5 Clone the repo on the server
```bash
cd /opt
sudo git clone https://github.com/YOUR_ORG/hackerspace-management.git hackerspace
sudo chown -R deploy:deploy /opt/hackerspace
cd /opt/hackerspace
```
### 6.6 Use the bundled Dockerfile and Compose file
Both files ship in the repo root:
```
Dockerfile (multi-stage build, standalone output, healthcheck)
docker-compose.yml (service definition, env_file, healthcheck)
.dockerignore
```
You do not need to write your own. The Dockerfile sets `DOCKER_BUILD=1` during build so `next.config.mjs` emits standalone output. The compose file reads `.env.local` by default; for production swap that to `.env.production` (next step) or edit the compose file inline.
### 6.7 Create the production env file
```bash
cp /opt/hackerspace/.env.example /opt/hackerspace/.env.production
nano /opt/hackerspace/.env.production
```
Fill in the real Supabase URL, anon key, service role key, and your app URL. Then lock the file:
```bash
chmod 600 /opt/hackerspace/.env.production
```
Update `docker-compose.yml` to point at `.env.production` instead of `.env.local`, or symlink:
```bash
ln -sf .env.production /opt/hackerspace/.env.local
```
### 6.8 Build and start the container
```bash
cd /opt/hackerspace
docker compose build
docker compose up -d
# Check logs
docker compose logs -f app
# Verify health endpoint
curl http://localhost:3000/api/health
# {"status":"ok","timestamp":"..."}
```
### 6.11 Configure Nginx as reverse proxy
```bash
sudo nano /etc/nginx/sites-available/hackerspace
```
Paste:
```nginx
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
```
Enable and test:
```bash
sudo ln -s /etc/nginx/sites-available/hackerspace /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
### 6.12 SSL with Let's Encrypt
Point your domain's A record to the Droplet IP first, then:
```bash
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
```
Certbot will auto-configure HTTPS and set up auto-renewal. Verify renewal works:
```bash
sudo certbot renew --dry-run
```
### 6.13 Set up automatic deployments (optional)
Create a deploy script:
```bash
cat > /opt/hackerspace/deploy.sh << 'EOF'
#!/bin/bash
set -e
cd /opt/hackerspace
git pull origin main
docker compose build app
docker compose up -d --no-deps app
docker image prune -f
echo "Deploy complete: $(date)"
EOF
chmod +x /opt/hackerspace/deploy.sh
```
To trigger a deploy from your local machine:
```bash
ssh deploy@YOUR_DROPLET_IP '/opt/hackerspace/deploy.sh'
```
Or set up a GitHub Actions workflow:
```yaml
# .github/workflows/deploy.yml
name: Deploy to Droplet
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DROPLET_IP }}
username: deploy
key: ${{ secrets.DROPLET_SSH_KEY }}
script: /opt/hackerspace/deploy.sh
```
Add `DROPLET_IP` and `DROPLET_SSH_KEY` to your GitHub repo's **Settings → Secrets**.
### 6.14 Firewall
```bash
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
```
---
## 7. Post-Deploy Checklist
Run through this after every fresh deployment:
- [ ] App loads at your domain over HTTPS
- [ ] `GET /api/health` returns `{ "status": "ok" }`
- [ ] `/signup` creates a user and redirects to `/dashboard`
- [ ] Dashboard renders without errors (counts may be zero)
- [ ] Supabase dashboard, Authentication > Users shows the signup
- [ ] `space_members` table has a row for the new user, with `role = 'admin'` for the first user (`createSpace` sets this)
- [ ] Chat page loads the three default channels (`general`, `announcements`, `ops`)
- [ ] Send a message and watch it appear in another tab without refresh (verifies realtime)
- [ ] Settings page saves without error
- [ ] PayPal integration accepts credentials and shows the connected badge
---
## 8. Upgrading
When the schema changes, a new numbered script is added to `scripts/` (for example `scripts/014_member_user_id_nullable.sql`). On an existing database, run only the new script in the Supabase SQL editor.
```
scripts/
schema.sql fresh deployment, full schema
001_create_schema.sql historical, not used for fresh deploys
...
014_member_user_id_nullable.sql most recent incremental
```
`scripts/schema.sql` is also idempotent: it uses `IF NOT EXISTS` for tables and indexes, `DROP POLICY IF EXISTS` before recreating policies, and `EXCEPTION WHEN duplicate_object` for enums and realtime publications. Re-running it on an existing database is safe but unnecessary.
For Vercel and App Platform: push to `main`, the build auto-deploys.
For Droplet: `ssh deploy@IP '/opt/hackerspace/deploy.sh'`
---
## 9. Troubleshooting
### "relation does not exist" errors
The schema was not applied or was partially applied. Re-run `scripts/schema.sql` in the Supabase SQL editor.
### Users can sign up but see no data
The `handle_space_signup` trigger is not firing or the `space_id` metadata was not passed during signup. Check:
```sql
SELECT * FROM public.space_members ORDER BY joined_at DESC LIMIT 5;
```
If empty, ensure a `spaces` row exists first:
```sql
INSERT INTO public.spaces (name, slug, invite_code)
VALUES ('My Hackerspace', 'my-hackerspace', 'INVITE123');
```
Then re-run signup with the space's `id` in the metadata.
### Chat messages not appearing in realtime
Verify realtime is enabled:
```sql
SELECT * FROM pg_publication_tables WHERE pubname = 'supabase_realtime';
```
Both `comms_messages` and `comms_channels` must appear. If not:
```sql
ALTER PUBLICATION supabase_realtime ADD TABLE public.comms_messages;
ALTER PUBLICATION supabase_realtime ADD TABLE public.comms_channels;
```
### 500 errors on API routes
Check that `SUPABASE_SERVICE_ROLE_KEY` is set in your deployment environment. This key is required for server-side actions.
### Docker container exits immediately
```bash
docker compose logs app
```
Most common cause: missing `.env.production` file or a missing required env var.
### SSL certificate not renewing
```bash
sudo systemctl status certbot.timer
sudo journalctl -u certbot
```
If the timer is not running: `sudo systemctl enable --now certbot.timer`