# Deployment persona warning

Customer VPS deployments (Hygo/High/etc.) must start from `.env.customer.example`. AskExe-owned control-plane variables live in `.env.askexe-control-plane.example` and must not be copied to customer VPSs.

# exe-os VPS stack — `deploy/compose/`

Full production stack for a single client VPS: CRM + wiki + gateway + exed
backed by Postgres, ClickHouse, and Redis. Pinned image tags, healthchecks on
every service, named volumes for persistence, and host-nginx-friendly port
publishing on `127.0.0.1`.

This is the **Lane A-2** deliverable from the v1.6 execution plan
(`exe/output/v16-execution-plan.md`). Pairs with **Lane A-1** Ansible roles
which provision the host and install nginx-tls; the `nginx-tls` role includes
the snippets at `../nginx/snippets/*.conf` from `/etc/nginx/snippets/`.

The standalone **gateway-only** compose at `deploy/docker-compose.yml` remains
unchanged; this directory is the full stack.

---

## Services

| Service        | Image                                         | Pin source             | Internal port |
|----------------|-----------------------------------------------|------------------------|---------------|
| `exe-db` | `pgvector/pgvector:pg16`                        | hard-pinned            | 5432          |
| `clickhouse`   | `clickhouse/clickhouse-server:24.8.4.13-alpine` | hard-pinned          | 8123 / 9000   |
| `redis`        | `redis:7.4-alpine`                            | hard-pinned            | 6379          |
| `exe-crm`      | `${CRM_IMAGE_TAG}`                            | `.env`                 | 3000          |
| `exe-crm-worker` | `${CRM_IMAGE_TAG}`                          | `.env`                 | —             |
| `exe-wiki`     | `${WIKI_IMAGE_TAG}`                           | `.env`                 | 3001          |
| `exe-os`       | `${EXE_OS_IMAGE_TAG}`                         | `.env`                 | 8765          |
| `exe-gateway`  | `${GATEWAY_IMAGE_TAG}`                        | `.env`                 | 3100 / 3101   |

DB minor pins reflect the LTS line per service. Bump in lockstep when the
upstream LTS rolls.

## Networks

- `backend` (`10.42.0.0/24`) — every service. Inter-service traffic only.
- `frontend` (`10.43.0.0/24`) — apps that terminate user traffic
  (`exe-crm`, `exe-wiki`, `exe-gateway`). DBs never join this net.

Apps publish their HTTP ports to `127.0.0.1` on the host so the host-installed
nginx (Lane A-1 `nginx-tls`) can reverse-proxy them. Override the host port via
`CRM_HOST_PORT` / `WIKI_HOST_PORT` / `GATEWAY_HTTP_HOST_PORT` /
`GATEWAY_WS_HOST_PORT` if you co-host multiple stacks.

## Company Brain projection guard

`exed` is the only place in the stack that should project cloud/local memory
into exe-db Postgres. This is **VPS-only** behavior. Founder/customer laptops
remain SQLite + encrypted cloud sync only.

The stack enables projection explicitly with:

```env
EXE_CLOUD_SYNC_TO_POSTGRES=true
DATABASE_URL=postgres://...
```

Code must not infer Postgres projection from a local `~/exe-db` checkout or a
stray `DATABASE_URL`; both the explicit gate and the DB URL are required.

## Volumes

`postgres_data`, `clickhouse_data`, `clickhouse_logs`, `redis_data`,
`crm_data`, `wiki_data`, `exe_os_data`, `gateway_data` — local driver, persisted
under `/var/lib/docker/volumes/exe-os_*`. Backups (Lane G, Wave 2) snapshot
these.

---

## Runbook

### 1. Validate config (no secrets needed)

```bash
cd deploy/compose/
cp .env.customer.example .env
docker compose -f docker-compose.yml config >/dev/null && echo OK
```

This is the `48224c7` invariant: parse must succeed on a fresh clone with
`.env.example` copied verbatim. The `env_file: required: false` gate keeps
this true even without `.env`. CI should run the same command.

### 2. Fill secrets

Edit `.env`. Replace every `CHANGEME_*` value. Set image tags to the versions
you want pinned for this client. Rotate secrets per the standard rotation
policy.

### 3. Boot the stack

```bash
docker compose -f docker-compose.yml up -d
docker compose -f docker-compose.yml ps
```

DBs come up first; apps wait on `condition: service_healthy`. Expect ~60s to
fully healthy on a fresh VPS.

`exe-gateway` also expects `deploy/compose/gateway.json` to exist. This repo
ships a minimal checked-in config that env overrides can layer on top of.

### 4. Wire host nginx

The Lane A-1 `nginx-tls` Ansible role drops `../nginx/snippets/*.conf` into
`/etc/nginx/snippets/` and renders per-host server blocks for
`crm.<client>.com`, `wiki.<client>.com`, `gateway.<client>.com` that
`include` the matching snippet. Reload nginx after `compose up -d`:

```bash
sudo nginx -t && sudo systemctl reload nginx
```

### 5. Verify end-to-end

```bash
curl -fsS https://crm.<client>.com/healthz       # exe-crm
curl -fsS https://wiki.<client>.com/api/ping     # exe-wiki
curl -fsS https://gateway.<client>.com/health    # exe-gateway
```

---

## Out of scope (handled by other Wave-1/2 lanes)

- Ansible host provisioning + nginx-tls — Lane A-1 (tom1)
- Image build pipelines for `exe-crm` / `exe-wiki` / `exed` / `exe-gateway` —
  consumers of the pinned tags; not built by this compose
- Daily backup cron — Lane G / I4 (Wave 2)
- Upgrade / rollback workflow — Lane K / I3 (Wave 2)

## Stack manifest updates (v0.9)

Customer VPS updates use the self-hosted omnibus model: customers update one
Exe OS stack version, not individual repo versions. The stack manifest pins the
compatible image tags for CRM, Wiki, exed, Gateway, and monitor agent.

Dry-run a local manifest:

```bash
exe-os stack-update \
  --manifest ./deploy/stack-manifests/v0.9.json \
  --compose-file ./deploy/compose/docker-compose.yml \
  --env-file ./deploy/compose/.env.example \
  --check
```

Run on a customer VPS after reviewing breaking-change notices:

```bash
exe-os stack-update \
  --manifest https://update.askexe.com/v1/stack-manifest.json \
  --allow-breaking whatsapp_relink_required \
  --yes
```

`stack-update` backs up the current `.env`, patches pinned image tags from the
manifest, runs `docker compose pull && docker compose up -d`, health-checks the
stack, writes `.exe-stack-lock.json`, and restores the previous `.env` if the
rollout fails.
