Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Ropeway Alignment — server config.
# Copy to your own env file (e.g. ~/.config/ropeway/env) and edit.
# See docs/DEPLOY_VERCEL_TUNNEL.md for the production deploy guide.

# ---- Core ----
# Postgres recommended in production. Default SQLite is fine for dev.
# ROPEWAY_DATABASE_URL=postgresql+psycopg://ropeway:****@localhost:5432/ropeway
ROPEWAY_DATABASE_URL=sqlite:///./data/server.db

# JWT signing key — generate once, keep secret:
# openssl rand -hex 32
ROPEWAY_SECRET_KEY=replace-with-openssl-rand-hex-32

# JWT lifetime (minutes). Default 12 h.
ROPEWAY_TOKEN_TTL_MIN=720

# ---- CORS (Vercel SPA origins) ----
# Comma-separated list. Use '*' only for local dev.
# At '*' the server auto-drops credentials (browsers reject the combo).
# Production: list every Vercel domain explicitly.
ROPEWAY_CORS_ORIGINS=https://your-app.vercel.app,https://your-app-git-main.vercel.app

# ---- P28: rate limit + API keys ----
# Per-key (or per-IP if no key) requests per minute. 0 disables.
ROPEWAY_RATE_LIMIT_PER_MIN=120

# Comma list of server-to-server API keys. The Vercel BFF puts one
# here and sends X-API-Key: <key> on every request. Empty disables
# API-key auth (JWT remains the only auth path).
# openssl rand -hex 24
ROPEWAY_API_KEYS=replace-with-openssl-rand-hex-24

# ---- COMM-1: OAuth (optional) ----
# ROPEWAY_GOOGLE_OAUTH_CLIENT_ID=
# ROPEWAY_GOOGLE_OAUTH_CLIENT_SECRET=
# ROPEWAY_GOOGLE_OAUTH_REDIRECT_URI=https://your-app.vercel.app/auth/google/callback
# ROPEWAY_MICROSOFT_OAUTH_CLIENT_ID=
# ROPEWAY_MICROSOFT_OAUTH_CLIENT_SECRET=
# ROPEWAY_MICROSOFT_OAUTH_REDIRECT_URI=https://your-app.vercel.app/auth/microsoft/callback

# ---- COMM-2: Razorpay billing (optional) ----
# ROPEWAY_RAZORPAY_KEY_ID=
# ROPEWAY_RAZORPAY_KEY_SECRET=
# ROPEWAY_RAZORPAY_PLAN_PROFESSIONAL=
# ROPEWAY_RAZORPAY_PLAN_ENTERPRISE=
# ROPEWAY_RAZORPAY_WEBHOOK_SECRET=

# ---- COMM-4: Observability (optional) ----
# ROPEWAY_SENTRY_DSN=
# ROPEWAY_SENTRY_TRACES=0.0
# ROPEWAY_PROMETHEUS=1
# ROPEWAY_STRUCTURED_LOGS=1
# ROPEWAY_RELEASE=v0.7.0

# ---- Misc ----
ROPEWAY_API_TITLE=Ropeway Alignment API
ROPEWAY_API_VERSION=0.7.0
ROPEWAY_JWT_ALG=HS256
ROPEWAY_TELEMETRY=0
195 changes: 195 additions & 0 deletions docs/DEPLOY_VERCEL_TUNNEL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Deploy: Vercel SPA → Cloudflare Tunnel → local compute box

The architecture this guide targets:

```
[ Engineer's browser ]
│ HTTPS
[ Vercel — Next.js SPA + edge functions ] ← static + thin backend-for-frontend
│ HTTPS via Cloudflare Tunnel
[ Your machine — FastAPI (ropeway serve) ] ← all computation lives here
Postgres / SQLite + DEM cache
```

Why this shape:

* **Free Vercel hosting** for the web interface.
* **Compute stays at home** — the GA, RL, DEM sampling, PyVista, etc. run on
your hardware. No cloud GPU bill.
* **Cloudflare Tunnel** punches a stable HTTPS URL to your box without
exposing a public port — Cloudflare terminates TLS and forwards to
`localhost:8000`.

## One-time setup on your box

### 1. Configure the FastAPI server

Set the production env vars. Pick concrete values for the bold ones — the
defaults are dev-only.

```bash
# In ~/.config/ropeway/env, or systemd EnvironmentFile=, or .envrc — your call.

# Database (PostgreSQL recommended in production):
ROPEWAY_DATABASE_URL=postgresql+psycopg://ropeway:****@localhost:5432/ropeway

# JWT signing key — generate once, keep secret:
ROPEWAY_SECRET_KEY=$(openssl rand -hex 32)

# CORS: list the Vercel preview + production domains explicitly.
# DO NOT use '*' here — the server will keep working but browsers will
# refuse cookie/credential requests with a wildcard origin.
ROPEWAY_CORS_ORIGINS=https://your-app.vercel.app,https://your-app-git-main.vercel.app

# P28 rate limit (per-IP-or-API-key, per minute). 0 disables.
ROPEWAY_RATE_LIMIT_PER_MIN=120

# P28 API keys — one per Vercel function that calls back to you.
# The Vercel BFF puts this in its env and sends X-API-Key on every request.
ROPEWAY_API_KEYS=vercel-bff-key-$(openssl rand -hex 12)

# Optional OAuth (COMM-1):
# ROPEWAY_GOOGLE_OAUTH_CLIENT_ID=...
# ROPEWAY_GOOGLE_OAUTH_CLIENT_SECRET=...
# ROPEWAY_GOOGLE_OAUTH_REDIRECT_URI=https://your-app.vercel.app/auth/google/callback

# Optional observability (COMM-4):
# ROPEWAY_SENTRY_DSN=...
# ROPEWAY_PROMETHEUS=1
# ROPEWAY_STRUCTURED_LOGS=1
```

A complete `.env.example` ships at the repo root.

### 2. Run the API as a service

Quick path (foreground):

```bash
ropeway serve --host 127.0.0.1 --port 8000
```

systemd unit (better — survives reboot, restart-on-crash):

```ini
# /etc/systemd/system/ropeway.service
[Unit]
Description=Ropeway Alignment FastAPI
After=network-online.target

[Service]
EnvironmentFile=/home/harsh-pandhe/.config/ropeway/env
WorkingDirectory=/home/harsh-pandhe/GitHub/Autonomous-Ropeway-Alignment
ExecStart=/home/harsh-pandhe/GitHub/Autonomous-Ropeway-Alignment/.venv/bin/ropeway serve --host 127.0.0.1 --port 8000
Restart=on-failure

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl enable --now ropeway.service
```

### 3. Install Cloudflare Tunnel

```bash
# Install cloudflared (Linux).
curl -fL -o /tmp/cloudflared.deb \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i /tmp/cloudflared.deb

# Authenticate (opens a browser to pick a zone).
cloudflared tunnel login

# Create a named tunnel + a public DNS hostname.
cloudflared tunnel create ropeway
cloudflared tunnel route dns ropeway api.your-domain.com
```

Tunnel config at `~/.cloudflared/config.yml`:

```yaml
tunnel: ropeway
credentials-file: /home/harsh-pandhe/.cloudflared/<tunnel-uuid>.json
ingress:
- hostname: api.your-domain.com
service: http://localhost:8000
- service: http_status:404
```

Run as a service:

```bash
sudo cloudflared service install
sudo systemctl enable --now cloudflared
```

Verify: `curl https://api.your-domain.com/health` → `{"status":"ok"}`.

### 4. Don't have a domain? Use a Quick Tunnel

```bash
cloudflared tunnel --url http://localhost:8000
# prints something like https://random-words-here.trycloudflare.com
```

Good for the engineer-trial phase. Move to a named tunnel + your domain
before you accept paying customers.

## Vercel side

The Next.js SPA (Phase P35+) ships with these env vars on Vercel:

```bash
ROPEWAY_API_BASE=https://api.your-domain.com
ROPEWAY_API_KEY=vercel-bff-key-... # matches one of ROPEWAY_API_KEYS
```

Pattern: Vercel functions (route handlers / server actions) call the local
box on the user's behalf, attaching the API key. The browser never sees the
key. End users authenticate to the SPA with password JWT or OAuth (Phase
COMM-1 + P26); the SPA forwards their JWT in addition to the BFF's
X-API-Key.

## Smoke-test the round trip

```bash
# 1. From anywhere, hit the tunnel:
curl -fsS https://api.your-domain.com/health

# 2. With API-key auth (when ROPEWAY_API_KEYS is set):
curl -fsS -H "X-API-Key: vercel-bff-key-..." \
https://api.your-domain.com/auth/oauth/providers
# {"providers": []} or whatever you configured

# 3. Rate limit smoke test (when ROPEWAY_RATE_LIMIT_PER_MIN > 0):
for i in $(seq 1 200); do
curl -s -o /dev/null -w "%{http_code} " https://api.your-domain.com/health
done
# Watch 200s flip to 429 once you cross the threshold.
```

## Operational notes

* **DEM cache.** The case studies download Copernicus tiles into
`data/dem/`. Put a few common tiles there before opening the tunnel —
`find_dem_tile` will warm up faster, and a tunnel restart never invalidates
the cache.
* **Database backups.** SQLite is in-repo (`data/server.db`); back up the
file. Postgres: `pg_dump` on a schedule.
* **Updates.** `git pull && pip install -e ".[server,dev,ui,viz3d,rl]" &&
systemctl restart ropeway`. Alembic migrations land in Phase P59.
* **Killing the tunnel** does not lose data — only the public URL goes away.
Restart `cloudflared` to come back online.

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| Browser CORS error from Vercel | wildcard `ROPEWAY_CORS_ORIGINS=*` + cookies | Set concrete Vercel origins; the server auto-drops credentials at `*` |
| `401 invalid or missing API key` | Vercel env `ROPEWAY_API_KEY` mismatch | Ensure it's in the server's `ROPEWAY_API_KEYS` comma list |
| `429` floods | Rate limit too tight under load | Raise `ROPEWAY_RATE_LIMIT_PER_MIN`, or move to Redis (P57) |
| Tunnel up, API 502 | systemd unit not running | `systemctl status ropeway; journalctl -u ropeway -n 100` |
14 changes: 12 additions & 2 deletions src/ropeway/server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,22 @@ def create_app(settings: Settings | None = None) -> FastAPI:
configure_sentry(release=settings.api_version)

app = FastAPI(title=settings.api_title, version=settings.api_version)
# P28b — CORS for the Vercel SPA. Browsers reject the
# ``allow_origins=['*']`` + ``allow_credentials=True`` combination,
# so we negotiate: if the operator left origins at the wildcard
# default, drop credentials (still useful for token-in-header auth
# like X-API-Key + Bearer JWT); when concrete origins are listed
# (e.g. https://your-app.vercel.app) we keep credentials so cookie-
# based session flows work too.
cors_origins = list(settings.cors_origins) or ["*"]
allow_credentials = "*" not in cors_origins
app.add_middleware(
CORSMiddleware,
allow_origins=list(settings.cors_origins) or ["*"],
allow_credentials=True,
allow_origins=cors_origins,
allow_credentials=allow_credentials,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["X-Request-ID", "Retry-After"],
)

# ---- Phase 14: htmx shareable-link demo (unauthenticated, synthetic) ----
Expand Down
Loading
Loading