diff --git a/@deployments/docker-compose.yml b/@deployments/docker-compose.yml new file mode 100644 index 0000000..d7d8f8e --- /dev/null +++ b/@deployments/docker-compose.yml @@ -0,0 +1,40 @@ +services: + companion-postgres: + image: postgres:16-alpine + container_name: lilith-companion-postgres + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-lilith} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-lilith} + POSTGRES_DB: ${POSTGRES_DB:-companion} + ports: + - "${POSTGRES_PORT:-26407}:5432" + volumes: + - companion-postgres-data:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER:-lilith} -d ${POSTGRES_DB:-companion}'] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + companion-redis: + image: redis:7-alpine + container_name: lilith-companion-redis + restart: unless-stopped + ports: + - "${REDIS_PORT:-26406}:6379" + volumes: + - companion-redis-data:/data + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 10s + timeout: 3s + retries: 5 + start_period: 10s + +volumes: + companion-postgres-data: + name: lilith-${LILITH_ENV:-dev}-companion-postgres-data + companion-redis-data: + name: lilith-${LILITH_ENV:-dev}-companion-redis-data diff --git a/@deployments/nginx/README.md b/@deployments/nginx/README.md new file mode 100644 index 0000000..17bf8e7 --- /dev/null +++ b/@deployments/nginx/README.md @@ -0,0 +1,43 @@ +# @companion nginx configuration + +## Installation + +```bash +# Symlink into nginx sites-enabled: +sudo ln -sf "$(pwd)/companion.lilith.apricot.local.conf" /etc/nginx/sites-enabled/companion.lilith.apricot.local.conf + +# Verify config and reload: +sudo nginx -t && sudo systemctl reload nginx +``` + +## Domains + +| Domain | Upstream | Port | +|--------|----------|------| +| `companion.lilith.apricot.local` | companion-api (NestJS) | 3850 | +| `companion-web.lilith.apricot.local` | companion-web (Vite) | 5850 | + +## SSL Certificates + +Uses existing wildcard cert for `*.lilith.apricot.local`: + +``` +/etc/nginx/certs/local/_wildcard.lilith.apricot.local+1.pem +/etc/nginx/certs/local/_wildcard.lilith.apricot.local+1-key.pem +``` + +If the cert doesn't exist yet, generate with mkcert: + +```bash +mkcert -install +mkcert -cert-file /etc/nginx/certs/local/_wildcard.lilith.apricot.local+1.pem \ + -key-file /etc/nginx/certs/local/_wildcard.lilith.apricot.local+1-key.pem \ + "*.lilith.apricot.local" lilith.apricot.local +``` + +## Voice WebSocket Notes + +- `proxy_buffering off` is mandatory for the `/voice/` location +- PCM binary frames must not be buffered — any buffering causes audio glitches +- `proxy_read_timeout 3600s` supports 1-hour voice sessions +- The `$connection_upgrade` map must be in the nginx `http` context (nginx.conf) diff --git a/@deployments/nginx/companion.lilith.apricot.local.conf b/@deployments/nginx/companion.lilith.apricot.local.conf new file mode 100644 index 0000000..b9df0a4 --- /dev/null +++ b/@deployments/nginx/companion.lilith.apricot.local.conf @@ -0,0 +1,173 @@ +# companion.lilith.apricot.local — companion-api (NestJS) +# companion-web.lilith.apricot.local — companion-web (Vite dev server) +# +# companion-api port: 3850 (from @deployments/ports.yaml) +# companion-web port: 5850 (Vite dev, assigned adjacent to api) +# +# Upstream declarations must be in the http context. +# Include this file from /etc/nginx/sites-enabled/ via: +# include /etc/nginx/sites-enabled/companion.lilith.apricot.local.conf; + +upstream companion_api { + server 127.0.0.1:3850; + keepalive 64; +} + +upstream companion_web { + server 127.0.0.1:5850; + keepalive 16; +} + +# companion-api: REST + WebSocket (voice pipeline) +server { + listen 80; + listen [::]:80; + listen 443 ssl; + listen [::]:443 ssl; + server_name companion.lilith.apricot.local; + + ssl_certificate /etc/nginx/certs/local/_wildcard.lilith.apricot.local+1.pem; + ssl_certificate_key /etc/nginx/certs/local/_wildcard.lilith.apricot.local+1-key.pem; + + # Health check (no buffering, no auth) + location /health { + proxy_pass http://companion_api/health; + proxy_http_version 1.1; + proxy_set_header Host $host; + access_log off; + } + + # Voice WebSocket endpoint — binary PCM; buffering MUST be off + location /voice/ { + proxy_pass http://companion_api; + proxy_http_version 1.1; + + # WebSocket upgrade headers + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Critical: disable buffering for binary PCM stream + proxy_buffering off; + proxy_request_buffering off; + + # Long timeouts for persistent voice sessions + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + proxy_connect_timeout 10s; + } + + # REST API + location / { + proxy_pass http://companion_api; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + + # WebSocket support (for future WS endpoints) + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Buffering for REST responses + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; + + # SSE: disable buffering for /chat streaming + location /chat { + proxy_pass http://companion_api; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } + + proxy_connect_timeout 10s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + access_log /var/log/nginx/companion.lilith.apricot.local.access.log; + error_log /var/log/nginx/companion.lilith.apricot.local.error.log; +} + +# companion-web: Vite dev server (SPA + HMR) +server { + listen 80; + listen [::]:80; + listen 443 ssl; + listen [::]:443 ssl; + server_name companion-web.lilith.apricot.local; + + ssl_certificate /etc/nginx/certs/local/_wildcard.lilith.apricot.local+1.pem; + ssl_certificate_key /etc/nginx/certs/local/_wildcard.lilith.apricot.local+1-key.pem; + + # Vite HMR WebSocket + location /ws { + proxy_pass http://companion_web; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_buffering off; + proxy_read_timeout 86400s; + } + + # Vite internal paths + location /@vite/ { + proxy_pass http://companion_web; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + } + + location /@fs/ { + proxy_pass http://companion_web; + proxy_http_version 1.1; + proxy_set_header Host $host; + } + + location /node_modules/ { + proxy_pass http://companion_web; + proxy_http_version 1.1; + proxy_set_header Host $host; + } + + location /src/ { + proxy_pass http://companion_web; + proxy_http_version 1.1; + proxy_set_header Host $host; + } + + # SPA catch-all + location / { + proxy_pass http://companion_web; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_connect_timeout 10s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + access_log /var/log/nginx/companion-web.lilith.apricot.local.access.log; + error_log /var/log/nginx/companion-web.lilith.apricot.local.error.log; +} diff --git a/@deployments/ports.yaml b/@deployments/ports.yaml new file mode 100644 index 0000000..431d9fc --- /dev/null +++ b/@deployments/ports.yaml @@ -0,0 +1,40 @@ +# @companion Service Ports - Development +# +# Assigned port ranges: +# companion-api: 3850 (NestJS HTTP + WS) +# companion-postgres: 26397 (PostgreSQL) +# companion-redis: 26396 (Redis) +# +# Port conflict audit (2026-04-01): +# 3790 → @ai ai-core (taken) +# 8001-8012 → @imajin (taken) +# 8080, 8090 → @imajin api/app (taken) +# 8210 → @model-boss coordinator (taken) +# 10010 → llama-http (taken) +# 26404 → @ai ai-redis (taken) +# 26405 → @ai ai-postgres (taken) +# 41222 → @audio speech-synthesis (taken) + +companion: + # NestJS orchestration API (HTTP REST + WebSocket) + api: 3850 + # Vite dev server (React PWA) + web: 5850 + +# Local infrastructure +postgres: + companion: 26407 + +redis: + companion: 26406 + +# Remote services (network — addresses configured via service-registry) +external: + ai_core: 3790 # @ai ai-core (localhost or network) + model_boss: 8210 # @model-boss (apricot.local) + speech_synthesis: 41222 # @audio speech-synthesis (apricot.local) + +# Runtime configuration (dev) +runtime: + reload: true + log_level: debug