diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index 01688b6..798700a 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -211,6 +211,7 @@ After Docker install, re-SSH so the `docker` group membership takes effect. | `company_site` | django (docker) | all webservers | prod | active/active behind NPM; beta port reserved | | `dta_service` | django (docker) | all webservers | beta + prod | active/active behind NPM | | `dta_webapp` | node/vite static | all webservers | beta + prod | active/active; built to `/var/www/.realpath.app/html`, served by web-static nginx | +| `dta_blog` | node-static (Python SSG) | all webservers | beta + prod | active/active; built to `/var/www/.blog.realpath.app/html`; **own hosts** (`blog.realpath.app` / `beta.blog.realpath.app`), not `realpath.app/blog`; Tianji ids baked at build | | `scha` | django (docker) | all webservers | prod | active/active behind NPM; beta port reserved | | `chat_web_app` | node-static (CRA) | all webservers | beta + prod | active/active; built to `/var/www/.chat.aimloperations/html`, served by web-static nginx | | `chat_backend` | django (docker) | all webservers | beta + prod | active/active behind NPM; Ollama `http://10.0.0.128:11434`; SearxNG `http://10.0.0.128:8088` (`SEARXNG_BASE_URL`) | @@ -256,6 +257,7 @@ future beta replica. | dta_webapp (nginx) | 8081 | 8080 | all webservers | | chat_web_app (nginx) | 8083 | 8082 | all webservers | | abc_fe (nginx) | **8085** | 8084 (*not deployed*) | all webservers | +| dta_blog (nginx) | **8087** | **8086** | all webservers | | SearxNG (LAN only) | — | **8088** | ai-server-4080 only (`searxng_stack`); not an NPM upstream | Host-local services on ai-server-4080 (not balanced by NPM): @@ -293,8 +295,10 @@ chat_backend secrets must use `SEARXNG_BASE_URL=http://10.0.0.128:8088`. `compose_profiles: [worker]` so deploy starts dj-queue with web. Other hosts omit profiles (web only). - **node-static**: git checkout at ref → `npm ci` → `npm run build:` - (writes to the app's `webroot_pattern`, e.g. `/var/www/{env}.realpath.app/html` - or `/var/www/{env}.chat.aimloperations/html`). + (writes to the app's `webroot_pattern`, e.g. `/var/www/{env}.realpath.app/html`, + `/var/www/{env}.blog.realpath.app/html`, or `/var/www/{env}.chat.aimloperations/html`). + Optional catalog `error_page_404` (used by `dta_blog`) serves that file instead + of the SPA `/index.html` fallback. - **web-static** role: one nginx container per app host serving the static roots on their ports (from `host_apps`); NPM balances across hosts. Before `compose up`, removes any container currently publishing those host ports (`docker ps --filter @@ -315,6 +319,7 @@ point each domain at the backend(s): | company_site | aimloperations.com (+ www) | `adama:8000` + `roslin:8000` + `starbuck:8000` + `apollo:8000` | | dta_service | (see DTA NPM hosts) | `adama:8001` / `8011` + same on roslin / starbuck / apollo / ai-server-4080 | | dta_webapp | (see DTA NPM hosts) | `adama:8080` / `8081` + same on roslin / starbuck / apollo | +| dta_blog | `blog.realpath.app` (prod); `beta.blog.realpath.app` (beta). **Do not** reverse-proxy onto `realpath.app/blog` | `adama:8086` / `8087` + same on roslin / starbuck / apollo / ai-server-4080 | | scha | `schawheaton.aimloperations.com`, `schawheaton.com` (+ www) | `adama:8002` + `roslin:8002` + `starbuck:8002` + `apollo:8002` (+ `ai-server-4080:8002`) | | chat_web_app | `chat.aimloperations.com` (+ www); `beta.chat.aimloperations.com` | `adama:8082` / `8083` + same on roslin / starbuck / apollo / ai-server-4080 | | chat_backend | `chatbackend.aimloperations.com`; `beta.chatbackend.aimloperations.com` | `adama:8003` / `8013` + same on roslin / starbuck / apollo / ai-server-4080 | @@ -337,6 +342,9 @@ point each domain at the backend(s): (keep the test/docker jobs). - [ ] `dta_webapp`: `npm run build:beta` / `build:prod` output to `/var/www/beta.realpath.app/html` / `/var/www/prod.realpath.app/html`. +- [ ] `dta_blog`: `npm run build:beta` / `build:prod` output to + `/var/www/beta.blog.realpath.app/html` / `/var/www/prod.blog.realpath.app/html` + (`python3 build.py --env ` copies `dist/`). Default branch is `main`. - [ ] `chat_web_app`: `npm run build:beta` / `build:prod` output to `/var/www/beta.chat.aimloperations/html` / `/var/www/prod.chat.aimloperations/html`. @@ -432,6 +440,22 @@ Companion workflows: [abc_be#22](https://git.aimloperations.com/GIS/abc_be/issue [abc_worker#27](https://git.aimloperations.com/GIS/abc_worker/issues/27), [abc_fe#27](https://git.aimloperations.com/GIS/abc_fe/issues/27). +`dta_blog` ([#29](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/29)) — +Python SSG, **no secrets file**. `npm run build:` writes +`/var/www/.blog.realpath.app/html`. Prod (`:8086`) omits `demo: true` seed +posts; beta (`:8087`) includes them. Header/footer links are baked at build +(`https://realpath.app` vs `https://beta.realpath.app`); Tianji website ids +are likewise baked (`cmtvvmf562afjzqumwt1yh2y8` / `cmtvvn6z62agdzqumtk8xijpy`). +Default branch is `main`. + +NPM / DNS / Cloudflare (Ansible does not manage these): `blog.realpath.app` → +`:8086`, `beta.blog.realpath.app` → `:8087`, TLS like the other RealPath names. +Do **not** mount this on `realpath.app/blog`. Static ports stay LAN / NPM-only +(UFW does not world-open 8086/8087). + +Companion app ticket: [dta_blog#1](https://git.aimloperations.com/Ditch_The_Agent/dta_blog/issues/1). +Gitea deploy can later call `./scripts/deploy.sh --app dta_blog --env beta|prod`. + ### One-time host bootstrap (per target) - [x] Gitea SSH key: the `gitea-key` role (in `site.yml`) generates a key per @@ -510,7 +534,8 @@ Store vault password for CI in a file readable only by the Act runner (e.g. `~/. | 10g | Register + deploy `url_shortening_service` (django, ports 8005/8015) | Done ([#22](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/22)) | | 10h | Register + deploy `college_craft` (django, ports 8006/8016) | Done ([#24](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/24)) | | 10i | Register + deploy ABC beta (`abc_be` 8017, `abc_worker` 8018, `abc_fe` 8085) | Done ([#26](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/26)) | -| 10j | Register + deploy `print_forge` (django, ports 8007/8019) | This PR ([#27](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/27)) | +| 10j | Register + deploy `print_forge` (django, ports 8007/8019) | Done ([#27](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/27)) | +| 10k | Register + deploy `dta_blog` (node-static, ports 8086/8087) | This PR ([#29](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/29)) | | 11 | Gitea container registry (optional) | Future | ## Open Decisions diff --git a/inventory/group_vars/all.yml b/inventory/group_vars/all.yml index f740bde..0d9a66d 100644 --- a/inventory/group_vars/all.yml +++ b/inventory/group_vars/all.yml @@ -80,6 +80,15 @@ app_catalog: webroot_pattern: "/var/www/{env}.realpath.app/html" # deploy runs `npm ci` then `npm run build:`; that script writes to # {{ web_static_root }}/_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: copies dist/ to this webroot + # so the existing node-static path (`npm ci` then `npm run build:`) 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" diff --git a/inventory/host_vars/adama.yml b/inventory/host_vars/adama.yml index 71d8cb0..5a4d9d5 100644 --- a/inventory/host_vars/adama.yml +++ b/inventory/host_vars/adama.yml @@ -11,6 +11,8 @@ host_apps: - { name: dta_service, env: beta, port: 8011 } - { name: dta_webapp, env: prod, port: 8080 } - { name: dta_webapp, env: beta, port: 8081 } + - { name: dta_blog, env: prod, port: 8086 } + - { name: dta_blog, env: beta, port: 8087 } - { name: scha, env: prod, port: 8002 } # optional: - { name: scha, env: beta, port: 8012 } - { name: chat_web_app, env: prod, port: 8082 } diff --git a/inventory/host_vars/ai-server-4080.yml b/inventory/host_vars/ai-server-4080.yml index 56e0a8c..bb4f78d 100644 --- a/inventory/host_vars/ai-server-4080.yml +++ b/inventory/host_vars/ai-server-4080.yml @@ -27,6 +27,8 @@ host_apps: - { name: company_site, env: prod, port: 8000 } - { name: dta_webapp, env: prod, port: 8080 } - { name: dta_webapp, env: beta, port: 8081 } + - { name: dta_blog, env: prod, port: 8086 } + - { name: dta_blog, env: beta, port: 8087 } - { name: dta_service, env: prod, port: 8001 } - { name: dta_service, env: beta, port: 8011 } - { name: scha, env: prod, port: 8002 } diff --git a/inventory/host_vars/apollo.yml b/inventory/host_vars/apollo.yml index c594086..242490a 100644 --- a/inventory/host_vars/apollo.yml +++ b/inventory/host_vars/apollo.yml @@ -7,6 +7,8 @@ host_apps: - { name: dta_service, env: beta, port: 8011 } - { name: dta_webapp, env: prod, port: 8080 } - { name: dta_webapp, env: beta, port: 8081 } + - { name: dta_blog, env: prod, port: 8086 } + - { name: dta_blog, env: beta, port: 8087 } - { name: scha, env: prod, port: 8002 } # optional: - { name: scha, env: beta, port: 8012 } - { name: chat_web_app, env: prod, port: 8082 } diff --git a/inventory/host_vars/roslin.yml b/inventory/host_vars/roslin.yml index 0234b71..f75fd6a 100644 --- a/inventory/host_vars/roslin.yml +++ b/inventory/host_vars/roslin.yml @@ -7,6 +7,8 @@ host_apps: - { name: dta_service, env: beta, port: 8011 } - { name: dta_webapp, env: prod, port: 8080 } - { name: dta_webapp, env: beta, port: 8081 } + - { name: dta_blog, env: prod, port: 8086 } + - { name: dta_blog, env: beta, port: 8087 } - { name: scha, env: prod, port: 8002 } # optional: - { name: scha, env: beta, port: 8012 } - { name: chat_web_app, env: prod, port: 8082 } diff --git a/inventory/host_vars/starbuck.yml b/inventory/host_vars/starbuck.yml index b9e2b0a..377fa6c 100644 --- a/inventory/host_vars/starbuck.yml +++ b/inventory/host_vars/starbuck.yml @@ -7,6 +7,8 @@ host_apps: - { name: dta_service, env: beta, port: 8011 } - { name: dta_webapp, env: prod, port: 8080 } - { name: dta_webapp, env: beta, port: 8081 } + - { name: dta_blog, env: prod, port: 8086 } + - { name: dta_blog, env: beta, port: 8087 } - { name: scha, env: prod, port: 8002 } # optional: - { name: scha, env: beta, port: 8012 } - { name: chat_web_app, env: prod, port: 8082 } diff --git a/roles/web-static/templates/nginx.conf.j2 b/roles/web-static/templates/nginx.conf.j2 index 6aac44c..686f33c 100644 --- a/roles/web-static/templates/nginx.conf.j2 +++ b/roles/web-static/templates/nginx.conf.j2 @@ -30,9 +30,17 @@ server { root {{ (app_catalog[a.name].webroot_pattern | default(web_static_root ~ '/{env}_' ~ a.name)) | replace('{env}', a.env) }}; index index.html; +{% if app_catalog[a.name].error_page_404 | default('') %} + error_page 404 {{ app_catalog[a.name].error_page_404 }}; + + location / { + try_files $uri $uri/ =404; + } +{% else %} location / { try_files $uri $uri/ /index.html; } +{% endif %} location ~* \.(?:js|css|woff2?|png|jpg|jpeg|gif|svg|ico)$ { expires 7d; diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 72967f0..422a8eb 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -17,7 +17,7 @@ Deploy applications with deploy-apps.yml. HOST Optional. Limit to one host: adama, roslin, starbuck, apollo, or ai-server-4080. Options: - --app NAME App to deploy (company_site, dta_service, dta_webapp, scha, chat_web_app, chat_backend, monica_site, url_shortening_service, college_craft, print_forge, abc_be, abc_worker, abc_fe) + --app NAME App to deploy (company_site, dta_service, dta_webapp, dta_blog, scha, chat_web_app, chat_backend, monica_site, url_shortening_service, college_craft, print_forge, abc_be, abc_worker, abc_fe) --env ENV Environment: beta or prod --ref REF Git ref/sha to deploy (default: master) --check Dry run