# Zyphon — Where things live & how to deploy (Docker)

Self-hosted messaging gateway: **no database**. Runtime state is YAML config + encrypted vault files + daily NDJSON logs.

For production on a separate VM, **Docker Compose is enough**.

---

## 1. Where is what

```
Zyphon/
├── apps/
│   ├── gateway/          # Fastify public API (:8080) + admin API (:8081)
│   └── dashboard/        # Next.js console (proxies /api/admin → gateway admin)
├── packages/             # Shared libs (types, config-loader, crypto, logger, api-client…)
├── providers/            # Email / SMS / WhatsApp adapters (SMTP, Twilio, Meta, India…)
├── config/               # Declarative YAML (mounted into the gateway container)
├── data/                 # Runtime: vault, logs, state (mounted volume — backup this)
├── secrets/              # Master key file for Docker (never commit real keys)
├── docs/                 # RULES, OpenAPI, this deploy guide
├── tests/                # Vitest contract + integration
├── Dockerfile            # Multi-stage: gateway | dashboard
└── docker-compose.yml    # gateway + dashboard (+ optional mailhog for local email)
```

| Path | Role |
|------|------|
| `config/*.yaml` | Apps, providers, routing, DLT, templates, alerts, suppressions |
| `data/state/vault.enc.json` | Encrypted provider credentials (AES-256-GCM) |
| `data/logs/zyphon-YYYY-MM-DD.log` | Append-only daily message/audit logs (NDJSON) |
| `docs/openapi.json` | Public + admin API schema |
| `docs/RULES.md` | Non-negotiable architecture / security rules |

---

## 2. Tech stack

| Layer | Choice |
|-------|--------|
| Runtime | Node.js **20** (Alpine in Docker) |
| Packages | **pnpm** workspaces + Turbo |
| Gateway | **Fastify** + Zod + Pino |
| Console | **Next.js 14** App Router + Tailwind |
| Config | YAML under `config/` (hot-reload / file watch) |
| Secrets | AES-256-GCM vault; master key from env or `/run/secrets/zyphon_master_key` |
| Tests | Vitest (+ Playwright for dashboard e2e) |
| Deploy | Docker multi-stage build + Compose |

**Not used:** Postgres, Redis, Kafka, SQLite, or any broker.

---

## 3. Ports

| Port | Service | Who calls it |
|------|---------|--------------|
| **8080** | Public API | Your apps (`POST /api/v1/messages`, health, webhooks) |
| **8081** | Admin API | Dashboard server-side proxy only (do **not** expose publicly if possible) |
| **3002** → container `3000` | Dashboard UI | Operators / developers in a browser |
| **2525** | SMTP relay (optional) | Only if enabled in `config/zyphon.yaml` |
| **1025 / 8025** | Mailhog (dev) | Local email sink / UI — omit on production VMs |

```
Browser ──► :3002 (Dashboard)
                │  rewrite /api/admin/*
                ▼
            :8081 (Admin API)  ◄── never put provider secrets in the browser

Your app ──► :8080 (Public API) ──► providers (SMTP / SMS / WhatsApp)
```

Firewall suggestion on a VM: open **80/443** (reverse proxy) + optionally **8080** for app traffic; keep **8081** on localhost or private network only.

---

## 4. Configuration

### Environment / secrets

| Variable / secret | Purpose |
|-------------------|---------|
| `ZYPHON_MASTER_KEY` **or** Docker secret `zyphon_master_key` | Exactly **32 bytes** (prefer **64 hex chars**). Encrypts the vault. |
| `ZYPHON_ADMIN_PASSWORD` | Console sign-in password (no username). |
| `ZYPHON_SYSTEM_PEPPER` | HMAC pepper for application API key verifiers. |
| `ZYPHON_VIEWER_PASSWORD` | Optional read-only console password. |
| `PORT` / `ADMIN_PORT` | Public / admin listen ports (defaults 8080 / 8081). |
| `ZYPHON_CONFIG_DIR` / `ZYPHON_DATA_DIR` | Paths inside container (`/app/config`, `/app/data`). |
| `INTERNAL_GATEWAY_URL` | Dashboard → gateway admin (Compose: `http://gateway:8081`). |
| `DASHBOARD_PORT` | Host port for UI (default **3002**). |

Copy [`.env.example`](../.env.example) as a reference. Real keys must **not** be committed.

### YAML under `config/`

| File | What it controls |
|------|------------------|
| `zyphon.yaml` | Host / ports / optional SMTP relay |
| `applications.yaml` | Apps, allowed channels, quotas, API keys metadata |
| `providers.yaml` | Provider instances (adapter type + non-secret config) |
| `routing.yaml` | Per-channel failover order, retries, timeouts, circuit breaker |
| `templates.yaml` | Message / DLT templates |
| `dlt.yaml` | India TRAI DLT (PE ID, headers, quiet hours) |
| `suppressions.yaml` | Blocked numbers / suppression lists |
| `alerts.yaml` | Alert thresholds / destinations |
| `team.yaml` | Team / operator labels (console) |
| `policies.yaml` | Extra policy knobs |

Provider **API keys / SMTP passwords** are **not** stored in YAML. They live in the encrypted vault via the Channels UI (or admin vault API).

---

## 5. Deploy on a separate VM (Docker)

### Prerequisites on the VM

- Linux (Ubuntu 22.04+ recommended)
- Docker Engine + Compose plugin (`docker compose version`)
- Open ports as needed (see §3)
- Git (to clone) or a release tarball

### Step-by-step

```bash
# 1) Clone
git clone <your-repo-url> zyphon && cd zyphon

# 2) Create a real master key (64 hex chars = 32 bytes) — DO NOT use the example in production
mkdir -p secrets data/logs data/state
openssl rand -hex 32 > secrets/master_key
chmod 600 secrets/master_key

# 3) Point Compose at your key file (edit docker-compose.yml secrets section)
#    secrets:
#      zyphon_master_key:
#        file: ./secrets/master_key

# 4) Set a strong admin password (edit docker-compose.yml environment, or use an env file)
#    ZYPHON_ADMIN_PASSWORD=<strong-unique-password>
#    ZYPHON_SYSTEM_PEPPER=<long-random-string>

# 5) Review config/ (providers, applications, routing, dlt) before first start

# 6) Build and start
docker compose up -d --build

# 7) Check health
curl -s http://127.0.0.1:8080/api/v1/health
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3002/
```

Open the console: `http://<vm-ip>:3002` (or via your reverse proxy). Sign in with `ZYPHON_ADMIN_PASSWORD`.

### Production tips

1. **Replace** default `ZYPHON_ADMIN_PASSWORD` and `ZYPHON_SYSTEM_PEPPER` before go-live.
2. Put **Nginx / Caddy / Traefik** in front: TLS for the dashboard; optionally terminate TLS for `:8080`.
3. Do **not** publish `:8081` on the public internet unless you must; the dashboard already reaches it on the Compose network.
4. Omit **Mailhog** on production (or remove that service from Compose).
5. Backup regularly: `config/` + `data/` (especially `data/state/` and `data/logs/`) and the master key file.
6. Losing the master key = **cannot decrypt the vault**. Store it offline / in a secrets manager.

### Optional reverse proxy (sketch)

- `https://zyphon.example.com` → `127.0.0.1:3002` (dashboard)
- `https://api.zyphon.example.com` → `127.0.0.1:8080` (public messaging API)

---

## 6. Start / stop / inspect (Docker is enough)

| Action | Command |
|--------|---------|
| **Start** (detached) | `docker compose up -d --build` |
| **Start** (foreground logs) | `docker compose up --build` |
| **Stop** (keep containers) | `docker compose stop` |
| **Stop & remove** | `docker compose down` |
| **Stop & wipe named volumes** (careful) | `docker compose down -v` |
| **Restart one service** | `docker compose restart gateway` |
| **Rebuild after code change** | `docker compose up -d --build` |
| **Logs (all)** | `docker compose logs -f` |
| **Logs (gateway)** | `docker compose logs -f gateway` |
| **Logs (dashboard)** | `docker compose logs -f dashboard` |
| **Status** | `docker compose ps` |
| **Shell into gateway** | `docker compose exec gateway sh` |

### Useful health checks

```bash
# Public API
curl -s http://127.0.0.1:8080/api/v1/health

# Dashboard
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3002/
```

### Local development without Docker (optional)

```bash
corepack enable
pnpm install
export ZYPHON_MASTER_KEY="$(openssl rand -hex 32)"
export ZYPHON_ADMIN_PASSWORD='choose-a-strong-password'
pnpm --filter @zyphon/gateway build && node apps/gateway/dist/index.js   # :8080 / :8081
pnpm --filter @zyphon/dashboard exec next dev -p 3002                    # UI
```

Stop with `Ctrl+C` in each terminal (or `pkill -f 'apps/gateway/dist/index.js'`).

---

## 7. What apps send

Your application → public API:

```http
POST http://<host>:8080/api/v1/messages
X-API-Key: <app-api-key>
Content-Type: application/json
```

Create apps and rotate keys in the **Apps** page of the dashboard. Channels, providers, and credentials are configured under **Channels**. Full payload shapes (text / HTML / templates / WhatsApp media) are in [`docs/openapi.json`](openapi.json) and the Apps → Developer tab.

---

## 8. Backup & recovery (short)

| Asset | Location |
|-------|----------|
| Declarative config | `config/` |
| Encrypted credentials | `data/state/` |
| Message history | `data/logs/` |
| Master key | `secrets/master_key` (or your secrets manager) |

Restore = restore those directories + same master key + `docker compose up -d`.

Security rules: [`docs/RULES.md`](RULES.md).
