Unknown URLs were returning the app shell with status 200. Return 404 for missing paths, keep the extensionless pages, and send the security headers. Production short links redirect for HeyCatch. Co-authored-by: Cursor <cursoragent@cursor.com>
228 lines
9.2 KiB
YAML
228 lines
9.2 KiB
YAML
---
|
|
ansible_user: westfarn
|
|
ansible_ssh_private_key_file: /home/westfarn/.ssh/ansible_deploy
|
|
ansible_python_interpreter: /usr/bin/python3
|
|
|
|
admin_user: westfarn
|
|
|
|
# UFW
|
|
ufw_ssh_port: 22
|
|
ufw_ssh_allowed_network: 10.0.0.0/24
|
|
ufw_allowed_tcp_ports:
|
|
- 80
|
|
- 443
|
|
|
|
# LiveKit on webservers. Signaling stays LAN (NPM → :7880). UDP must be
|
|
# reachable from the internet via router DNAT to the NPM upstream host.
|
|
# 8020 is abc_voice. That process uses host networking, so Docker does not
|
|
# publish the port and UFW drops it unless it is listed here. NPM
|
|
# (abc.voice.aimloperations.com) reaches it from the LAN.
|
|
ufw_lan_tcp_ports:
|
|
- 7880
|
|
- 7881
|
|
- 8020
|
|
ufw_extra_udp_ports:
|
|
- 3478
|
|
- "50000:60000"
|
|
|
|
# Docker
|
|
docker_users:
|
|
- "{{ admin_user }}"
|
|
|
|
# Tianji monitoring
|
|
tianji_server_url: https://tianji.aimloperations.com
|
|
tianji_workspace_id: cm7w8087y020lddswyhamadj2
|
|
tianji_install_script_url: "https://tianji.aimloperations.com/serverStatus/{{ tianji_workspace_id }}/install.sh?url={{ tianji_server_url }}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Observability (Alloy on every host → Loki/Prometheus/Grafana on control node)
|
|
# See docs/OBSERVABILITY.md
|
|
# ---------------------------------------------------------------------------
|
|
loki_url: "http://10.0.0.128:3100"
|
|
loki_push_url: "{{ loki_url }}/loki/api/v1/push"
|
|
prometheus_url: "http://10.0.0.128:9090"
|
|
prometheus_remote_write_url: "{{ prometheus_url }}/api/v1/write"
|
|
grafana_public_url: "https://grafana.aimloperations.com"
|
|
grafana_public_domain: "grafana.aimloperations.com"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# App deployment (Phase 2)
|
|
# ---------------------------------------------------------------------------
|
|
git_base_url: "ssh://git@git.aimloperations.com:30009"
|
|
|
|
# Where app checkouts and per-app env files live on each target host.
|
|
apps_base_dir: /opt/apps
|
|
apps_env_dir: "{{ apps_base_dir }}/env" # persistent .env files, never in git
|
|
apps_src_dir: "{{ apps_base_dir }}/src" # git checkouts
|
|
|
|
# Control-node directory holding per-app .env secrets, pushed to hosts at
|
|
# deploy time. Layout: <secrets_dir>/<app>/<app>_<env>.env (never in git).
|
|
secrets_dir: "{{ lookup('ansible.builtin.env', 'HOME') }}/Documents/secrets"
|
|
|
|
# Static site document root base (dta_webapp writes build output here).
|
|
web_static_root: /var/www
|
|
|
|
# Catalog of deployable apps. host_apps (per host_vars) references these by name.
|
|
# Optional host_apps.enabled: false skips deploy (stop.sh still compose-downs).
|
|
app_catalog:
|
|
company_site:
|
|
type: django
|
|
repo: "{{ git_base_url }}/ai_ml_operations/company_site.git"
|
|
default_branch: master
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
migrate_cmd: "uv run python manage.py migrate --noinput"
|
|
dta_service:
|
|
type: django
|
|
repo: "{{ git_base_url }}/Ditch_The_Agent/dta_service.git"
|
|
default_branch: main
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
migrate_cmd: "uv run python manage.py migrate --noinput"
|
|
# Pre-docker deploy used venv + systemd on each host; stop before compose up.
|
|
legacy_systemd_units:
|
|
beta: beta_dta_wsgi
|
|
prod: prod_dta_wsgi
|
|
dta_webapp:
|
|
type: node-static
|
|
repo: "{{ git_base_url }}/Ditch_The_Agent/dta_webapp.git"
|
|
default_branch: main
|
|
# package.json lives in this subdir of the checkout (npm runs here).
|
|
subdir: ditch-the-agent
|
|
# Document root served by nginx and written by `npm run build:<env>`.
|
|
# {env} is replaced with the entry's env (beta/prod).
|
|
webroot_pattern: "/var/www/{env}.realpath.app/html"
|
|
# deploy runs `npm ci` then `npm run build:<env>`; that script writes to
|
|
# {{ web_static_root }}/<env>_dta_webapp (beta/prod), served by web-static.
|
|
dta_blog:
|
|
type: node-static
|
|
repo: "{{ git_base_url }}/Ditch_The_Agent/dta_blog.git"
|
|
default_branch: main
|
|
# Python SSG (stdlib). package.json build:<env> copies dist/ to this webroot
|
|
# so the existing node-static path (`npm ci` then `npm run build:<env>`) works.
|
|
webroot_pattern: "/var/www/{env}.blog.realpath.app/html"
|
|
# Real 404 page (not SPA index fallback). See roles/web-static nginx.conf.j2.
|
|
error_page_404: /404.html
|
|
scha:
|
|
type: django
|
|
repo: "{{ git_base_url }}/ai_ml_operations/scha.git"
|
|
default_branch: master
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
migrate_cmd: "uv run python manage.py migrate --noinput"
|
|
chat_web_app:
|
|
type: node-static
|
|
repo: "{{ git_base_url }}/ai_ml_operations/chat_web_app.git"
|
|
default_branch: master
|
|
# package.json lives in this subdir of the checkout (npm runs here).
|
|
subdir: llm-fe
|
|
# Document root served by nginx and written by `npm run build:<env>`.
|
|
# {env} is replaced with the entry's env (beta/prod).
|
|
webroot_pattern: "/var/www/{env}.chat.aimloperations/html"
|
|
# hesychia.ai Stripe/RevenueCat webhooks hit this vhost. Proxy the prefix
|
|
# to chat_backend so POST is not answered 405 by the SPA index.html.
|
|
api_backend: chat_backend
|
|
api_proxy_prefix: /api/finance/webhooks/
|
|
# /robots.txt, /sitemap.xml, and /llms.txt must not fall through to index.html.
|
|
exact_root_files: true
|
|
# Homepage was missing these. Enforcing CSP stays loose so Tianji, the API,
|
|
# websockets, and inline styles keep working. Report-Only is the strict policy.
|
|
security_headers: true
|
|
# Unknown URLs are HTTP 404 with public/404.html, not 200 + the SPA shell.
|
|
# Slash routes are directory index.html files from scripts/stampCanonicals.js.
|
|
# These extensionless routes are the matching files (keep in sync with
|
|
# llm-fe/src/llm-fe/utils/canonicals.js paths that do not end in /).
|
|
error_page_404: /404.html
|
|
html_exact_paths:
|
|
- /legal/terms-of-service
|
|
- /billing/success
|
|
- /billing/cancel
|
|
- /document_storage
|
|
chat_backend:
|
|
type: django
|
|
repo: "{{ git_base_url }}/ai_ml_operations/chat_backend.git"
|
|
default_branch: master
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
migrate_cmd: "uv run python manage.py migrate --noinput"
|
|
# Host header the static-site webhook proxy sends. Must be in Django
|
|
# ALLOWED_HOSTS (see chat_backend DJANGO_ALLOWED_HOSTS).
|
|
public_hosts:
|
|
prod: chatbackend.aimloperations.com
|
|
beta: beta.chatbackend.aimloperations.com
|
|
monica_site:
|
|
type: django
|
|
repo: "{{ git_base_url }}/ai_ml_operations/monica_site.git"
|
|
default_branch: main
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
migrate_cmd: "uv run python manage.py migrate --noinput"
|
|
# dj-queue worker = compose profile `worker` (singleton). Deploy starts it
|
|
# via host_apps.compose_profiles on adama only (see host_vars/adama.yml).
|
|
url_shortening_service:
|
|
type: django
|
|
repo: "{{ git_base_url }}/ai_ml_operations/url_shortening_service.git"
|
|
default_branch: master
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
migrate_cmd: "uv run python manage.py migrate --noinput"
|
|
# No compose worker profile. Two public NPM hosts share one container/port.
|
|
college_craft:
|
|
type: django
|
|
repo: "{{ git_base_url }}/ai_ml_operations/college_craft.git"
|
|
default_branch: master
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
migrate_cmd: "uv run python manage.py migrate --noinput"
|
|
# dj-queue worker = compose profile `worker` (singleton). Deploy starts it
|
|
# via host_apps.compose_profiles on adama only (see host_vars/adama.yml).
|
|
print_forge:
|
|
type: django
|
|
repo: "{{ git_base_url }}/ai_ml_operations/print_forge.git"
|
|
default_branch: master
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
migrate_cmd: "uv run python manage.py migrate --noinput"
|
|
# dj-queue worker = compose profile `worker` (singleton). Deploy starts it
|
|
# via host_apps.compose_profiles on adama only (see host_vars/adama.yml).
|
|
abc_be:
|
|
type: django
|
|
repo: "{{ git_base_url }}/GIS/abc_be.git"
|
|
default_branch: Development
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
migrate_cmd: "uv run python manage.py migrate --noinput"
|
|
abc_worker:
|
|
type: django
|
|
repo: "{{ git_base_url }}/GIS/abc_worker.git"
|
|
default_branch: Development
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
# FastAPI — no Django migrations. `true` keeps the django deploy path happy.
|
|
migrate_cmd: "true"
|
|
# Same git repo as abc_worker, second container (voice join). Not the text worker.
|
|
abc_voice:
|
|
type: django
|
|
repo: "{{ git_base_url }}/GIS/abc_worker.git"
|
|
default_branch: Development
|
|
compose_file: docker-compose.voice.yml
|
|
web_service: web
|
|
migrate_cmd: "true"
|
|
abc_fe:
|
|
type: node-static
|
|
repo: "{{ git_base_url }}/GIS/abc_fe.git"
|
|
default_branch: Development
|
|
webroot_pattern: "/var/www/{env}.abc.aimloperations/html"
|
|
livekit:
|
|
type: django
|
|
repo: "{{ git_base_url }}/GIS/livekit.git"
|
|
default_branch: master
|
|
compose_file: docker-compose.prod.yml
|
|
web_service: web
|
|
# SFU — no Django migrations. `true` keeps the django deploy path happy.
|
|
migrate_cmd: "true"
|
|
|
|
# Deploy filter vars. CI passes these; manual runs may leave them undefined
|
|
# to (re)deploy every app listed in the host's host_apps.
|
|
# app: <name> app_env: beta|prod app_ref: <git sha or branch, default master>
|