# VPS HTML Deployment

This is the production shape for the briefing publisher. n8n is deliberately
not part of this VPS compose stack. The scheduler remains on the local
workstation and calls the VPS agent through an SSH tunnel.

## Components on the VPS

- `city_briefs_db`: PostgreSQL database for `city_briefs_ops`
- `city-briefing-agent`: FastAPI orchestrator and HTML writer
- `city_briefs_web`: Nginx static server for `/brief/<slug>` and `og-cover.jpg`
- Existing Nginx Proxy Manager: public HTTPS entry point
- No n8n container

The agent writes HTML to the host directory:

```text
/var/www/just2done.com/brief/<slug>.html
```

The web container mounts that directory read-only. This separates writing from
serving and avoids `scp`/`rsync` during every generated brief.

## Host preparation

Run on the actual Docker host, not inside a development sandbox:

```bash
mkdir -p /var/www/just2done.com/brief
chmod 755 /var/www/just2done.com/brief
cp assets/abstraction-media-tickets.jpg /var/www/just2done.com/brief/og-cover.jpg
docker network inspect proxy >/dev/null 2>&1 || docker network create proxy
```

The existing Nginx Proxy Manager container must also be attached to `proxy`.

## Compose deployment

From the project directory on the VPS:

```bash
docker compose -f docker-compose.yml -f docker-compose.vps.yml \
  up -d --build briefs-db briefs-web city-briefing-agent
```

Do not start n8n from this project.

Verify the containers and the internal web path:

```bash
curl -fsS http://127.0.0.1:8002/health
curl -fsSI http://127.0.0.1:8085/brief/og-cover.jpg
```

## Nginx Proxy Manager route

Create a Proxy Host for `just2done.com`:

- Scheme: `http`
- Forward hostname: `city_briefs_web`
- Forward port: `80`
- Network: attach Nginx Proxy Manager and `city_briefs_web` to `proxy`
- SSL: request a Let's Encrypt certificate for `just2done.com`
- Force SSL: enabled after certificate issuance
- Access list/authentication: none for `/brief/` because crawlers must read OG metadata without authentication

The Nginx container handles extensionless URLs:

```text
/brief/toronto-aug-11-2026
    -> /usr/share/nginx/html/brief/toronto-aug-11-2026.html
```

## Local n8n connection

The local workstation n8n should not use the Docker service name
`city-briefing-agent` for production. Open a tunnel from the workstation:

```bash
ssh -N \
  -L 18002:127.0.0.1:8002 \
  -i ~/.ssh/show-city-briefing-agent_ed25519 \
  dev_automation@178.162.240.152
```

The local n8n HTTP Request node should use:

```text
http://host.docker.internal:18002/api/v1/brief/run
```

The Header Auth credential remains the same bearer credential already stored
in local n8n. The tunnel keeps the agent API private; only SSH port 22 needs to
be reachable from the workstation.

## Required public checks

```bash
curl -fsSI https://just2done.com/brief/og-cover.jpg
curl -fsSI https://just2done.com/brief/<slug>
```

The agent's final verification must pass:

- HTML URL returns HTTP 200
- HTML is accessible without authentication
- `og:title`, `og:description`, `og:image`, and `og:url` exist
- OG image returns HTTP 200 with an `image/*` content type

## Current blocker

At the time this document was created, `just2done.com` DNS/TLS was not yet
configured for `178.162.240.152`, and the provided SSH account landed in a
restricted sandbox without Docker host networking. Complete the host-level
Docker access and DNS/Nginx Proxy Manager setup before switching local n8n to
the VPS tunnel target.
