# Deploying Trail Guide to the Lifestream VPS

Puts the pilot behind **https://trailguide.lifestream.org** for Wayne to test.
Everything here is already true or already built:

- Host: Lifestream InMotion cPanel/WHM VPS (13 GB RAM / 8 cores — huge headroom).
- Python **3.11.9** installed alongside the system 3.6.8 (`alt-python311`), at
  `/opt/alt/python311/bin/python3.11`. The system 3.6.8 stays untouched.
- cPanel user: **lifest74**. App home: **/home/lifest74/trailguide**.
- Subdomain **trailguide.lifestream.org** created, with a working SSL cert.
- Local store already built (`store/chunks.json` + `store/vectors.npy`).

**Who runs what:** `root` does only the three system steps (5, 6, and the module
check). Everything else runs as **lifest74**. The app never runs as root. From a
root shell you can become the user with `su - lifest74`.

The config files referenced below live in `deploy/`.

---

## 1. Upload the app  (as lifest74, or rsync from your Mac)

From your Mac, in the pilot folder, push everything the app needs **except** the
local venv, caches, and raw media (only the transcripts are needed, and the store
already contains them):

```bash
rsync -avz --delete \
  --exclude '.venv' --exclude 'venv' --exclude '__pycache__' \
  --exclude '.DS_Store' --exclude 'content/media' --exclude 'content/audio' \
  ./  lifest74@lifestream.org:/home/lifest74/trailguide/
```

Runtime needs the code, `store/`, the `*.csv` catalogs, `system_prompt.txt`, and
`guide-notes.txt` — the rsync above includes all of them. (`content/` is only
needed to re-ingest later; keep the text under it, skip the big audio/video.)

## 2. Build the venv on Python 3.11  (as lifest74)

```bash
cd /home/lifest74/trailguide
/opt/alt/python311/bin/python3.11 -m venv venv
source venv/bin/activate
python -V                       # should say Python 3.11.9
pip install --upgrade pip

# Install CPU-only PyTorch FIRST so pip doesn't pull ~1.5 GB of unused CUDA libs:
pip install torch --index-url https://download.pytorch.org/whl/cpu

pip install -r requirements.txt
```

If any package tries to compile (rare — most ship wheels) and fails, install the
dev headers as root once: `yum install alt-python311-devel gcc gcc-c++`, then retry.

## 3. Create the environment file  (as lifest74)

```bash
cp deploy/env.production.example .env
python -c "import secrets; print(secrets.token_hex(32))"   # paste into FLASK_SECRET
nano .env                                                  # add the real API key
chmod 600 .env
```

## 4. Smoke-test by hand before wiring it up  (as lifest74)

```bash
source venv/bin/activate
set -a; . ./.env; set +a
gunicorn --workers 1 --threads 8 --timeout 120 --bind 127.0.0.1:8020 app:app
```

First start downloads the ~500 MB embedding model from Hugging Face (one time).
You should see a line like `[Trail Guide] Loaded NN,NNN chunks | store built ...`.
In a second terminal:

```bash
curl -s -X POST http://127.0.0.1:8020/ask \
  -H 'Content-Type: application/json' -d '{"q":"What is grace?"}' | head
```

Streaming JSON lines = working. `Ctrl-C` to stop, then wire up the service.

> If the model can't reach Hugging Face (locked-down egress), pre-seed it: on your
> Mac copy `~/.cache/huggingface` up to `/home/lifest74/.cache/huggingface`.

## 5. Install the systemd service  (as root)

```bash
cp /home/lifest74/trailguide/deploy/trailguide.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now trailguide
systemctl status trailguide          # active (running)?
journalctl -u trailguide -n 30       # see the startup / store-loaded line
```

It now runs as lifest74, restarts on failure, and comes back after a reboot.

## 6. Reverse-proxy the subdomain  (as root)

First confirm mod_proxy is present (EasyApache4 → Apache Modules → `mod_proxy`,
`mod_proxy_http`). Then drop in the include and rebuild:

```bash
mkdir -p /etc/apache2/conf.d/userdata/ssl/2_4/lifest74/trailguide.lifestream.org
cp /home/lifest74/trailguide/deploy/trailguide.include.conf \
   /etc/apache2/conf.d/userdata/ssl/2_4/lifest74/trailguide.lifestream.org/trailguide.conf
/scripts/ensure_vhost_includes --user=lifest74
/scripts/rebuildhttpdconf
systemctl restart httpd
```

**If a cPanel/Engintron NGINX layer sits in front of Apache** (NGINX Manager or
Engintron installed), it will buffer the stream and break the live typing. Fix:
in NGINX Manager disable caching for this subdomain, or add `proxy_buffering off;`
for it. No NGINX layer → nothing to do.

## 7. End-to-end test

Open **https://trailguide.lifestream.org**, hard-refresh, ask a question, and
confirm the answer *streams in* (types out) rather than appearing all at once —
that proves buffering is off through the whole chain. Check a cited link works.

## 8. Lock the spend ceiling

Because the URL is unlisted but ungated, set a **low monthly cap** on this key's
Anthropic account (Console → Settings → Limits) — e.g. $10–20. That cap is the
real ceiling; the cookie turn-cap is only a courtesy.

## 9. Hand it to Wayne

Send him the URL plus a two-line note: ask anything, and when an answer feels off,
tell Greg the exact question. His main tuning lever is `system_prompt.txt` (voice
and guardrails). You'll have the full record in `logs/qa-YYYYMM.jsonl`.

---

## Operating it later

```bash
# restart after a change / re-ingest
sudo systemctl restart trailguide

# watch logs
journalctl -u trailguide -f                       # service / errors
tail -f /home/lifest74/trailguide/logs/qa-*.jsonl # what people asked

# re-ingest after content or catalog changes (as lifest74)
cd /home/lifest74/trailguide && source venv/bin/activate
python3 ingest.py && sudo systemctl restart trailguide
```

To add a shared password later (if the unlisted URL ever leaks), the cleanest spot
is HTTP Basic Auth in the same Apache include — a ~15-minute change.
