# Deployment guide

Step-by-step instructions to take this system from a local development
checkout to a running production deployment on your VPS. Two deployment
paths are supported — pick one, do not mix them on the same server.

- **Path A: bare-metal with systemd** (deployment/systemd/) — slightly
  more manual setup, marginally lower overhead, more direct control.
- **Path B: Docker Compose** (deployment/docker/) — one command brings
  up the whole stack, easier to reproduce and tear down.

Both are production-viable at the scale described in this project.

## Prerequisites

- A VPS running Ubuntu 22.04 or 24.04, with at least 4GB RAM and 2 CPU
  cores as a starting point (scale up once you've measured real load —
  see `tests/load/locustfile.py`)
- A domain name with DNS pointed at the VPS's IP address (required for
  SSL — skip this for an initial HTTP-only internal test, but a client
  handover should always be HTTPS)
- SSH access to the VPS

## Local development first

Before touching the VPS at all, verify everything works locally:

```bash
# 1. Clone/extract the project, then:
cp .env.example .env
# Edit .env: set a real ANTHROPIC_API_KEY at minimum to test the LLM layer

# 2. Set up Python environment
python3.12 -m venv venv
source venv/bin/activate
pip install -r requirements-dev.txt

# 3. Start Postgres and Redis locally (e.g. via Docker for local dev convenience)
docker run -d -p 5432:5432 -e POSTGRES_USER=crop_user -e POSTGRES_PASSWORD=crop_pass -e POSTGRES_DB=crop_db postgres:16-alpine
docker run -d -p 6379:6379 redis:7-alpine

# 4. Run database migrations
alembic upgrade head

# 5. Run the test suite — this should pass with NO real models or data needed
pytest tests/unit tests/integration

# 6. Get real (or synthetic placeholder) data and run the ML pipeline
#    See docs/data_sourcing.md for where to get real data.
#    For a first local test, you can run each preprocessing/training
#    script individually, or the full pipeline at once:
python -m ml_pipeline.training.run_full_pipeline \
    --raw-climate data/raw/imd/rainfall.csv \
    --raw-crop data/raw/icrisat/yield.csv \
    --raw-market data/raw/agmarknet/prices.csv

# 7. Validate the trained model bundle
python -m ml_pipeline.evaluation.validate_model_bundle --model-dir data/models/v1

# 8. Run the API locally
uvicorn app.main:app --reload --port 8000

# 9. In a separate terminal, run a Celery worker
celery -A app.workers.celery_app worker --loglevel=info

# 10. Test it
curl -X POST http://localhost:8000/api/v1/analyze \
  -H "X-API-Key: dev-local-key-change-me" \
  -H "Content-Type: application/json" \
  -d '{"crop": "rice", "location": "thanjavur", "month": "June"}'
# Returns a task_id — poll it:
curl http://localhost:8000/api/v1/tasks/{task_id} -H "X-API-Key: dev-local-key-change-me"
```

Confirm this entire flow works locally before packaging anything for
the VPS. This is also exactly what to demonstrate to your project
coordinator for approval before deployment.

## Packaging for upload

Once approved locally:

```bash
# From the project root, excluding what .gitignore already excludes
# (venv, __pycache__, raw data, trained models if you're transferring
# them separately, .env with real secrets)
zip -r crop-ai-system.zip . \
    -x "venv/*" -x "*__pycache__*" -x ".git/*" \
    -x "data/raw/*" -x ".env"
```

Trained model files (`data/models/v1/*.pkl`) are large and you may
prefer to transfer them separately via `scp` rather than bundling in
the zip, especially over a slow connection.

## Path A: bare-metal deployment with systemd

```bash
# 1. SSH into the VPS, then provision it (one-time, as root/sudo)
sudo bash deployment/scripts/provision_server.sh

# 2. Upload the project zip and extract it to /opt/crop-ai-system
scp crop-ai-system.zip user@your-vps-ip:/tmp/
ssh user@your-vps-ip
sudo mkdir -p /opt/crop-ai-system
sudo unzip /tmp/crop-ai-system.zip -d /opt/crop-ai-system
sudo chown -R cropai:cropai /opt/crop-ai-system

# 3. Switch to the cropai user and set up environment
sudo su - cropai
cd /opt/crop-ai-system
cp .env.example .env
nano .env  # fill in REAL production values: DATABASE_URL with the real
           # postgres password, ANTHROPIC_API_KEY, VALID_API_KEYS,
           # ENVIRONMENT=production, etc.

# 4. Upload model artifacts (if not already in the zip)
# From your local machine:
scp -r data/models/v1/* user@your-vps-ip:/opt/crop-ai-system/data/models/v1/

# 5. Run the deploy script
bash deployment/scripts/deploy.sh

# 6. Install systemd service files (as root, one-time)
exit  # back to your sudo user
sudo cp /opt/crop-ai-system/deployment/systemd/*.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable crop-ai-api crop-ai-worker
sudo systemctl start crop-ai-api crop-ai-worker

# 7. Install Nginx config
sudo cp /opt/crop-ai-system/deployment/nginx/crop-ai-system.conf /etc/nginx/sites-available/
# Edit it: replace YOUR_DOMAIN.com with the real domain
sudo nano /etc/nginx/sites-available/crop-ai-system.conf
sudo ln -s /etc/nginx/sites-available/crop-ai-system.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

# 8. Set up SSL (once DNS is pointed at this server)
sudo bash /opt/crop-ai-system/deployment/scripts/setup_ssl.sh your-domain.com

# 9. Verify
curl https://your-domain.com/api/v1/health/ready
```

### Redeploying after changes

```bash
sudo su - cropai
cd /opt/crop-ai-system
# Upload new code (zip/scp as before), then:
bash deployment/scripts/deploy.sh
```

## Path B: Docker Compose deployment

```bash
# 1. Provision the server with Docker (one-time)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# log out and back in for the group change to take effect

# 2. Upload the project and model artifacts as in Path A steps 2 and 4

# 3. Set up .env as in Path A step 3

# 4. Build and start the full stack
cd /opt/crop-ai-system
docker compose -f deployment/docker/docker-compose.yml up -d --build

# 5. Run database migrations
docker compose -f deployment/docker/docker-compose.yml exec api alembic upgrade head

# 6. Verify
curl http://127.0.0.1:8000/api/v1/health/ready

# 7. Set up Nginx + SSL on the HOST (outside Docker) pointing at
#    127.0.0.1:8000, exactly as in Path A steps 7-8
```

### Scaling workers under load

```bash
docker compose -f deployment/docker/docker-compose.yml up -d --scale worker=4
```

## Post-deployment checklist

- [ ] `curl https://your-domain.com/api/v1/health/ready` returns `"status": "ready"`
- [ ] All 6 models show in the readiness check's `loaded_models` list
- [ ] A real `POST /analyze` + poll cycle returns a complete result
- [ ] SSL certificate is valid (`https://` works, no browser warnings)
- [ ] `/docs` and `/redoc` are NOT publicly accessible (disabled in production by design — see `app/main.py`)
- [ ] `/metrics` is NOT publicly accessible (firewalled to internal IPs in Nginx config)
- [ ] Database backups are scheduled (`crontab -e` as the `cropai` user, see `deployment/scripts/backup.sh`)
- [ ] Sentry DSN is configured if error tracking is wanted (`SENTRY_DSN` in `.env`)
- [ ] Run a load test against the live deployment before handing off (`locust -f tests/load/locustfile.py --host=https://your-domain.com`)
- [ ] Client has been briefed on data licensing status (see `docs/data_sourcing.md`)
