# Deploying Polleo Demand (first-time guide)

This puts the app online for ~6 users in about an hour, on one small Linux
server, using Docker. The **same files work on your provider's server later** —
you'll just repeat steps 7–9 there.

What runs: 3 containers — **db** (Postgres), **api** (FastAPI), **web** (Caddy:
serves the React app + HTTPS + proxies `/api`).

---

## 0. Before you start (on your Windows machine)
You need:
- The dev stack working locally (so we can copy your current data out). The dev
  Postgres container is `polleo_db` — check it's running: `docker ps`.
- An **SSH key**. If you don't have one: `ssh-keygen -t ed25519` (press Enter
  through the prompts). Your public key is in `C:\Users\Korisnik\.ssh\id_ed25519.pub`.

---

## 1. Get a server (≈10 min)
Any Ubuntu 22.04/24.04 box works. Cheapest good option — **Hetzner Cloud**:
1. console.hetzner.cloud → New Project → New Server.
2. Location: **Germany (EU)**. Image: **Ubuntu 24.04**.
3. Type: **CPX21** (3 vCPU / 4 GB) — enough for 6 users + forecast runs.
4. Add your **SSH key** (paste the `.pub` contents). Create.
5. Note the **public IP** (e.g. `203.0.113.42`).

SSH in from your machine:
```
ssh root@203.0.113.42
```

---

## 2. Install Docker on the server (≈3 min)
```
curl -fsSL https://get.docker.com | sh
docker version          # confirm it works
```

---

## 3. Put the app on the server
**Option A — Git (recommended).** If the repo is on GitHub/GitLab (private is
fine), on the server:
```
cd /opt
git clone <your-repo-url> polleo-demand
cd polleo-demand
```
**Option B — no remote: copy from your machine.** In a *local* terminal, make a
tarball without the heavy/cruft folders, then copy it up:
```
# Windows PowerShell, from the project folder:
tar --exclude=.git --exclude=frontend/node_modules --exclude=.venv -czf polleo.tgz .
scp polleo.tgz root@203.0.113.42:/opt/
# then on the server:
mkdir -p /opt/polleo-demand && tar -xzf /opt/polleo.tgz -C /opt/polleo-demand
cd /opt/polleo-demand
```
(`data/` — your config + seed files — comes along either way.)

---

## 4. Export your current data (run LOCALLY, on Windows)
This dumps everything in your dev database to one file:
```
docker exec -t polleo_db pg_dump -U polleo --no-owner --no-acl -d polleo_demand > polleo_dump.sql
scp polleo_dump.sql root@203.0.113.42:/opt/polleo-demand/
```

---

## 5. Configure secrets (on the server, in /opt/polleo-demand)
```
cp .env.prod.example .env
nano .env
```
Fill in:
- `POSTGRES_PASSWORD` → `openssl rand -base64 24`
- `JWT_SECRET` → `python3 -c "import secrets; print(secrets.token_urlsafe(64))"`
  (or `openssl rand -base64 64 | tr -d '\n'`)
- `SITE_ADDRESS` → `app.<IP-with-dashes>.sslip.io` (e.g. `app.203-0-113-42.sslip.io`)
- `CORS_ORIGINS` → `["https://app.203-0-113-42.sslip.io"]` (must match, JSON array)
- Leave `ANTHROPIC_API_KEY`, `POLLEO_AI_DATABASE_URL`, `INGEST_API_KEY` empty for
  now (the app runs fine; Polleo AI just shows "not configured").

Save (Ctrl+O, Enter, Ctrl+X).

---

## 6. Build & start the database first
```
docker compose -f docker-compose.prod.yml up -d --build db
docker compose -f docker-compose.prod.yml ps    # wait until db is "healthy"
```

## 7. Load your data into it
```
docker compose -f docker-compose.prod.yml exec -T db \
  psql -U polleo -d polleo_demand < polleo_dump.sql
```
You may see a few harmless NOTICE/role lines — that's fine. Quick check:
```
docker compose -f docker-compose.prod.yml exec db \
  psql -U polleo -d polleo_demand -c "SELECT count(*) FROM dim_products;"
```

## 8. Start the rest
```
docker compose -f docker-compose.prod.yml up -d --build
docker compose -f docker-compose.prod.yml ps    # db, api, web all "running"
docker compose -f docker-compose.prod.yml logs -f api   # watch startup; Ctrl+C to stop watching
```
Caddy fetches an HTTPS cert on first hit — the very first request can take ~20s.

---

## 9. Open it & point users
Browse to **`https://app.<IP-with-dashes>.sslip.io`**.
Log in with your **existing app credentials** (your users + passwords came over
in the data restore). Give the 6 users their logins.

Forgot a password? Reset from inside the running api:
```
docker compose -f docker-compose.prod.yml exec api \
  python -c "from backend.models.database import SessionLocal; from backend.services.auth_service import set_password; db=SessionLocal(); set_password(db,'USERNAME','NEWPASS'); print('done')"
```
(If that helper differs in your code, use Admin → User Management once you're in.)

---

## 10. Optional hardening (recommended within a day)
- **Lock to office IPs:** edit `deploy/Caddyfile`, uncomment the `@blocked` lines
  with your office IP range, then `docker compose -f docker-compose.prod.yml up -d --build web`.
- **Firewall:** `ufw allow OpenSSH && ufw allow 80 && ufw allow 443 && ufw enable`.
- **Polleo AI:** put a **company** Anthropic key in `.env` (`ANTHROPIC_API_KEY=...`)
  and `docker compose -f docker-compose.prod.yml up -d api`.

---

## 11. Day-to-day
- **Update after a code change:** `git pull` (or re-copy) →
  `docker compose -f docker-compose.prod.yml up -d --build`. Data is safe in the
  `pgdata` volume; it is NOT wiped by rebuilds.
- **Logs:** `docker compose -f docker-compose.prod.yml logs -f api`
- **Backup the DB (do this daily):**
  ```
  docker compose -f docker-compose.prod.yml exec -T db \
    pg_dump -U polleo --no-owner --no-acl -d polleo_demand > backup_$(date +%F).sql
  ```
- **Restart everything:** `docker compose -f docker-compose.prod.yml restart`

---

## 12. Handoff to your provider (in ~1 month)
Give them this repo + `DEPLOY.md`. They repeat **steps 3, 5–8** on the company
server (their own `.env`, their own `SITE_ADDRESS`/domain). Move the data with a
fresh `pg_dump` (step 4 → step 7) so it's current. Nothing else changes.

---

### Troubleshooting
- **api keeps restarting** → `logs api`. Usual causes: a `.env` value (JWT_SECRET
  < 32 chars, or `CORS_ORIGINS` not a JSON array), or the db wasn't healthy yet.
- **HTTPS won't issue** → port 80 must be open to the internet and `SITE_ADDRESS`
  must resolve to this server's IP (sslip.io does this automatically). Check
  `logs web`.
- **A page/view looks empty but data exists** → refresh the materialized views:
  `docker compose -f docker-compose.prod.yml exec db psql -U polleo -d polleo_demand -c "REFRESH MATERIALIZED VIEW v_sales_weekly_full;"`
