# Polleo Demand — Deployment Guide

This guide is for the **server admin** deploying Polleo Demand to a Linux box
on the company intranet. Audience: ~6 users (DP, KAM, CM, admin). Single-host
deployment with PostgreSQL + FastAPI + nginx serving a Vite-built React app.

## Prereqs on the target host

- Ubuntu 22.04+ or RHEL 9+ (anything with systemd)
- Python 3.12 (3.14 NOT supported — scipy wheel issues)
- PostgreSQL 16+
- nginx + certbot
- Node.js 20+ (only for the frontend build step)
- A non-root user `polleo` to own the app files

## 1. Clone + python env

```bash
# As root or via sudo:
useradd --system --create-home --shell /bin/bash polleo
mkdir -p /var/www/html/nabava.polleosport.com /var/www/html/nabava.polleosport.com/logs
chown -R polleo:polleo /var/www/html/nabava.polleosport.com

# As polleo user:
sudo -iu polleo
cd /var/www/html/nabava.polleosport.com
git clone <repo-url> .
python3.12 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
```

## 2. PostgreSQL setup

```bash
# As postgres user
sudo -iu postgres
createuser --pwprompt polleo        # use a strong password, NOT polleo_dev
createdb --owner polleo polleo_demand
exit

# As polleo user, initialize schema
cd /var/www/html/nabava.polleosport.com
source .venv/bin/activate
python db/init_db.py                # creates tables
```

Then **load production data** (sales / stock / costs / suppliers / etc.):

```bash
python update_sales.py              # pulls ERP CSVs into Postgres
python recalc_uplift_erp.py         # uplift recalculation
```

## 3. Environment config

```bash
cp .env.example .env
$EDITOR .env
```

Fill in:

| Variable | What |
|---|---|
| `APP_ENV` | `production` |
| `DATABASE_URL` | `postgresql://polleo:STRONG_PW@localhost:5432/polleo_demand` |
| `JWT_SECRET` | Run `python -c "import secrets; print(secrets.token_urlsafe(64))"` and paste |
| `JWT_EXPIRE_HOURS` | `168` (= 7 days, default for internal use) |
| `CORS_ORIGINS` | `https://nabava.polleosport.com` (no trailing slash) |
| `POLLEO_SLACK_TOKEN` | Only if Slack integration used |

```bash
chmod 600 .env                      # restrict to owner only
```

> If `APP_ENV=production` and `JWT_SECRET` is the dev default OR CORS contains
> a localhost entry, the app **refuses to start** with a clear error message.

## 4. First-time password setup

The dev-mode "any password" backdoor has been removed. Users must have a
bcrypt-hashed password. Bootstrap with:

```bash
cd /var/www/html/nabava.polleosport.com
source .venv/bin/activate
python scripts/init_passwords.py
```

This prints a 6-char password for every user without one — **copy them out
of the terminal immediately**. They are bcrypt-hashed in DB right after,
not recoverable.

To rotate a single user later:

```bash
python scripts/init_passwords.py --user lovro
```

Or reset everyone (for security incident):

```bash
python scripts/init_passwords.py --all
```

Users can change their own password via the UI (Account → Change password)
which calls `POST /api/auth/change-password`.

## 5. Frontend build

```bash
cd /var/www/html/nabava.polleosport.com/frontend
npm ci
npm run build
# Output ends up in /var/www/html/nabava.polleosport.com/frontend/dist/
```

If the API origin differs from the page origin, you may need to set
`VITE_API_BASE_URL` at build time — by default the client uses `/api` and
nginx proxies that to FastAPI on the same host.

## 6. systemd unit for FastAPI

```bash
sudo cp /var/www/html/nabava.polleosport.com/deploy/nabava-api.service \
        /etc/systemd/system/nabava-api.service
sudo systemctl daemon-reload
sudo systemctl enable --now nabava-api
sudo systemctl status nabava-api
```

Tail logs:

```bash
journalctl -u nabava-api -f
```

If the service refuses to start, the **first 10 lines** of `journalctl`
will show why. Common causes: bad `DATABASE_URL`, dev `JWT_SECRET` with
`APP_ENV=production`, missing venv.

## 7. nginx + TLS

```bash
# Issue a Let's Encrypt cert (or use your internal CA)
sudo certbot certonly --nginx -d nabava.polleosport.com

# Install the nginx site
sudo cp /var/www/html/nabava.polleosport.com/deploy/nginx.conf.example \
        /etc/nginx/sites-available/polleo-demand
sudo ln -s /etc/nginx/sites-available/polleo-demand /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```

Open `https://nabava.polleosport.com` in a browser → login screen.

## 8. Smoke test

```bash
# Health (public)
curl https://nabava.polleosport.com/api/health
# → {"status":"ok","db":"connected", ...}

# Login (replace with real pw printed by init_passwords.py)
curl -X POST https://nabava.polleosport.com/api/auth/login \
     -H 'Content-Type: application/json' \
     -d '{"username":"lovro","password":"zd4n6b"}'
# → {"token":"...","user":{...}}

# Authenticated call
TOKEN=...
curl https://nabava.polleosport.com/api/supply/health -H "Authorization: Bearer $TOKEN"
```

## 9. Backups

PostgreSQL dump cron — daily, retain 14 days:

```cron
# /etc/cron.d/polleo-demand-backup
0 3 * * * postgres pg_dump polleo_demand | gzip > /var/backups/polleo/$(date +\%Y\%m\%d).sql.gz
0 4 * * * root find /var/backups/polleo -mtime +14 -delete
```

Verify restore works at least once before going live.

## 10. Updating the app

```bash
sudo -iu polleo
cd /var/www/html/nabava.polleosport.com
git pull
source .venv/bin/activate
pip install -r requirements.txt          # in case deps changed
cd frontend && npm ci && npm run build   # rebuild frontend
exit
sudo systemctl restart nabava-api
# nginx reload only needed if static config changed
```

## 11. Common operations

| Task | Command |
|---|---|
| Restart backend | `sudo systemctl restart nabava-api` |
| View logs | `journalctl -u nabava-api -n 200 -f` |
| Reset one user password | `python scripts/init_passwords.py --user NAME` |
| Reload nginx | `sudo systemctl reload nginx` |
| Stop everything | `sudo systemctl stop nabava-api nginx` |
| DB shell | `sudo -iu postgres psql polleo_demand` |
| Refresh sales data | `python update_sales.py` |
| Run forecast batch | `python run_backtest.py` (review output first) |

## 12. Security model — explicit assumptions

- App is **internal-only** — exposed only on the company intranet / VPN.
- No public internet exposure. If that changes, add WAF + DDoS protection.
- All users have a real bcrypt password (no dev-mode bypass).
- Login rate-limited (5 fails/60s → 60s lockout per IP).
- JWT tokens expire after 7 days; no server-side revoke (stateless).
- HTTPS enforced by nginx (HSTS header set, HTTP → 301 redirect).
- Admin role required for `/api/admin/*` endpoints (user management).
- Token stored in browser sessionStorage — clears when tab closes.

## 13. Known limitations

- No multi-host deployment (single-instance app)
- No automated tests in CI
- No staging environment — changes go straight to prod after local test
- No 2FA (consider for the future)
- No audit log of logins (could be added to `audit_log` table easily)
- Logs to journald only; no centralised log shipping
- No metrics / dashboards (Grafana/Prometheus optional add-on)

For the internal-app use case (~6 users, intranet-only, single source
of operational truth) these limits are accepted trade-offs.
