Compare commits

..
9 Commits
Author SHA1 Message Date
westfarn 209b27142c Register GIS LiveKit as docker compose on webservers.
Sync runner checkout / sync (pull_request) Successful in 7s
Same deploy path as abc_worker: host_apps on adama/starbuck/etc., NPM for wss://. UDP/7881 still forwarded to one upstream host.
2026-09-15 14:39:10 -05:00
westfarn 1988def1a6 Register dta_blog for node-static deploy (closes #29) (#30)
Sync runner checkout / sync (push) Successful in 7s
## Summary
- Closes [#29](#29).
- Register `dta_blog` in `app_catalog` (`type: node-static`, repo `Ditch_The_Agent/dta_blog`, default branch `main`, webroot `/var/www/{env}.blog.realpath.app/html`). Reuses `roles/app-deploy/tasks/node_static.yml` (`npm ci` then `npm run build:<env>`); the blog's `package.json` copies `dist/` to that webroot after `python3 build.py --env <env>`.
- Add prod **8086** / beta **8087** `host_apps` on adama, roslin, starbuck, apollo, and ai-server-4080 (those ports were free vs 8080–8085 / 8088).
- Optional nginx `error_page 404 /404.html` for this app only (`error_page_404` catalog field); other static apps keep the SPA `try_files` fallback.
- Document NPM/DNS (`blog.realpath.app` → `:8086`, `beta.blog.realpath.app` → `:8087`), no `realpath.app/blog` mount, and UFW staying LAN/NPM-only.

## Test plan
- [ ] Confirm 8086/8087 unused on app hosts before first deploy.
- [ ] `./scripts/deploy.sh --app dta_blog --env beta --ref main` publishes `/var/www/beta.blog.realpath.app/html`.
- [ ] `./scripts/deploy.sh --app dta_blog --env prod --ref main` publishes `/var/www/prod.blog.realpath.app/html`.
- [ ] **Until [dta_blog#1](Ditch_The_Agent/dta_blog#1) is on `main`**, use `--ref issue-1-static-blog` so the SSG (not the stub README) is built.
- [ ] NPM + DNS + TLS: `blog.realpath.app` → `:8086`, `beta.blog.realpath.app` → `:8087`. Do not reverse-proxy onto `realpath.app/blog`.
- [ ] Prod HTML has article text, Tianji id `cmtvvmf562afjzqumwt1yh2y8`, links to `https://realpath.app/`.
- [ ] Beta HTML has Tianji id `cmtvvn6z62agdzqumtk8xijpy`, links to `https://beta.realpath.app/`, demo posts present.
- [ ] `https://blog.realpath.app/sitemap.xml`, `robots.txt`, `llms.txt` return 200.
- [ ] Unknown slug returns 404.html (not the index SPA fallback).

Reviewed-on: #30
2026-09-10 18:34:25 -07:00
westfarn 2abcdd7c58 Register print_forge for django deploy (closes #27) (#28)
Sync runner checkout / sync (push) Successful in 6s
## Summary
- Closes [#27](#27).
- Register `print_forge` in `app_catalog` (`type: django`, repo `ai_ml_operations/print_forge`, default branch `master`).
- Add `host_apps` prod `:8007` / beta `:8019` on **adama**, **roslin**, **starbuck**, **apollo**, and **ai-server-4080**.
- dj-queue **worker singleton on adama** only (`compose_profiles: [worker]`); other hosts web-only.
- Document ports, NPM hosts, Postgres DBs, secrets, Nominatim (`10.0.0.128:8089`), and the app-repo companion ([print_forge#1](ai_ml_operations/print_forge#1)).
- Update `scripts/deploy.sh` `--app` help.
- Move unused `abc_be` prod port reserve **8007 → 8009** so it does not collide with `print_forge`.

## Test plan
- [ ] Confirm `print_forge` appears in `app_catalog` and `--app print_forge` is listed in `deploy.sh --help`
- [ ] Confirm beta `host_apps` on all five hosts (port **8019**); adama has `compose_profiles: [worker]`
- [ ] Confirm prod port **8007** reserved on those hosts (no NPM until launch)
- [ ] After merge (ops, not this PR): create Postgres DBs `print_forge_beta` + `print_forge` on `10.0.0.230`, grant `westfarn`
- [ ] After merge (ops): write `~/Documents/secrets/print_forge/print_forge_{beta,prod}.env` from app `.env.prod.example`; add `print_forge:<token>` to `SHORTENER_API_TOKENS` (beta first)
- [ ] After merge (ops): NPM `print-forge-preview.aimloperations.com` → `:8019` active/active; do **not** NPM-route `printforgeprints.com` until launch
- [ ] After secrets + DBs: `~/Documents/repos/server-infra/scripts/deploy.sh --app print_forge --env beta --ref master` and hit `/healthz/`

Reviewed-on: #28
2026-09-06 06:35:48 -07:00
westfarnandCursor aac815314d Register abc_fe / abc_be / abc_worker for beta-only deploy.
Sync runner checkout / sync (push) Successful in 6s
Closes #26. GIS Benefits Coach gets beta host_apps (8017/8018/8085) and catalog entries; prod ports stay reserved.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 16:21:23 -05:00
westfarn 95fbae8db4 Register college_craft for django deploy (closes #24) (#25)
Sync runner checkout / sync (push) Successful in 6s
## Summary
- Closes [#24](#24).
- Register `college_craft` in `app_catalog` (`type: django`, repo `ai_ml_operations/college_craft`, default branch `master`).
- Add `host_apps` prod `:8006` / beta `:8016` on **adama**, **roslin**, **starbuck**, **apollo**, and **ai-server-4080**.
- dj-queue **worker singleton on adama** only (`compose_profiles: [worker]`); other hosts web-only.
- Document ports, NPM hosts, Postgres DBs, secrets, Nominatim (`10.0.0.128:8089`), and the app-repo `--app client_site` leftover.
- Add `college_craft` to `scripts/deploy.sh` `--app` help.

## Out of scope (ops, after merge)
- Create Postgres DBs `college_craft` / `college_craft_beta` on `10.0.0.230` and grant `westfarn`.
- Write control-node secrets `~/Documents/secrets/college_craft/college_craft_{prod,beta}.env`.
- NPM: `collegecraft.com` (+ www) → `:8006`; `college-craft-preview.aimloperations.com` → `:8016`.
- Smoke deploy after secrets + DBs exist:
  ```bash
  ~/Documents/repos/server-infra/scripts/deploy.sh --app college_craft --env beta --ref master
  ~/Documents/repos/server-infra/scripts/deploy.sh --app college_craft --env prod --ref master
  ```

## Test plan
- [ ] Confirm `app_catalog.college_craft` matches other django apps (`compose_file`, `migrate_cmd`).
- [ ] Confirm ports 8006/8016 unused elsewhere and match across all five hosts.
- [ ] Confirm only adama has `compose_profiles: [worker]` for college_craft.
- [ ] After secrets + DBs: deploy beta then prod; `/healthz/` up on both; prod holding page, beta full site.

Reviewed-on: #25
2026-09-02 07:39:03 -07:00
westfarn 9208e63491 Register url_shortening_service for django deploy (closes #22) (#23)
Sync runner checkout / sync (push) Successful in 6s
## Summary
- Closes [#22](#22).
- Register `url_shortening_service` in `app_catalog` (`type: django`, repo `ai_ml_operations/url_shortening_service`, default branch `master`) and `host_apps` on **adama**, **roslin**, **starbuck**, **apollo**, and **ai-server-4080** (prod **8005**, beta **8015**).
- Document NPM (short host vs API host, same container), Postgres DBs (`url_shortener` / `url_shortener_beta`), and extra env keys. No compose worker profile.
- List `url_shortening_service` in `scripts/deploy.sh` `--app` help.

## Out of scope (manual ops)
- Create Postgres DBs `url_shortener` / `url_shortener_beta` + grant `westfarn` on `10.0.0.230`
- Control-node secrets under `~/Documents/secrets/url_shortening_service/`
- NPM proxy hosts (Ryan): short host `/` + `/[a-z0-9]{4,8}` (404 `/api/`, `/admin/`, `/debug/`); API host `/api/` only (404 `/admin/`)
- Deploy smoke — [url_shortening_service](https://git.aimloperations.com/ai_ml_operations/url_shortening_service) `master` is still a stub (`README.md` only)

## Test plan
- [x] Inventory YAML: `url_shortening_service` in `app_catalog`; every host `host_apps` has prod **8005** / beta **8015**
- [ ] After app compose + secrets + DBs exist: `./scripts/deploy.sh --app url_shortening_service --env beta --ref master`
- [ ] Same for prod
- [ ] `GET /healthz/` → `{"status":"ok"}` on both ports
- [ ] Short host `GET /` landing page; `POST /api/links/` without Bearer → 401; public `/admin/` → 404

Reviewed-on: #23
2026-08-30 04:51:31 -07:00
westfarn ec294a1d45 Add starbuck and apollo as active/active app hosts (#21)
Sync runner checkout / sync (push) Successful in 6s
## Summary

- Closes [#20](#20).
- Register **starbuck** (`10.0.0.44`) and **apollo** (`10.0.0.7`) as Proxmox app hosts in `webservers`, with `host_apps` copied from **roslin** (same apps/ports; **no** `monica_site` worker — that stays a singleton on **adama**).
- Allow `starbuck` / `apollo` in `provision.sh` and `deploy.sh`; document inventory, NPM backends, and Alloy `host` labels.

## Test plan

- [ ] `ansible-inventory --list` shows both hosts under `webservers` at the expected IPs.
- [ ] Bootstrap SSH + passwordless sudo on each VM (keep a Proxmox console open for first UFW enable).
- [ ] `ansible starbuck,apollo -m ping`
- [ ] `./scripts/provision.sh starbuck --check` then `./scripts/provision.sh starbuck`
- [ ] `./scripts/provision.sh apollo --check` then `./scripts/provision.sh apollo`
- [ ] `./scripts/deploy.sh starbuck` and `./scripts/deploy.sh apollo` bring up the same app+env set as roslin.
- [ ] `monica_site` dj-queue worker still runs **only** on adama.
- [ ] Grafana/Loki show `host="starbuck"` and `host="apollo"`.
- [ ] NPM Advanced upstreams (manual): add `starbuck:PORT` and `apollo:PORT` beside adama/roslin.Reviewed-on: #21
2026-08-16 07:20:38 -07:00
westfarn 2e3fe13b39 Build profile-gated services so workers do not run stale images (#19)
Sync runner checkout / sync (push) Successful in 7s
## Summary

The django deploy sets `COMPOSE_PROFILES` on the **start** step but not on the **build** step, so `docker compose build` skips profile-gated services. `up -d` then reuses whatever image already exists and the container silently keeps running old code.

This adds `COMPOSE_PROFILES` to the build step so it matches the start step. Only affects hosts that set `host_apps.compose_profiles` (today: the `monica_site` dj-queue worker on adama).

## Symptom this fixes

On adama the beta worker image was 20 hours stale while web was current:

| Image | Built | Postgres driver |
|---|---|---|
| `monica_site_beta-web` | today | psycopg 3.3.4 |
| `monica_site_beta-worker` | Aug 8 | psycopg2 2.9.12 |

So the worker crash-looped on LISTEN/NOTIFY (`TypeError: 'list' object is not callable` in `dj_queue/runtime/notify.py`) long after `monica_site` had moved to psycopg3, because its image was never rebuilt.

Branch is merged up with `master`, which already carries the worker auto-start from [#18](#18); the diff here is just the build step.

## Test plan

- [x] Manual `COMPOSE_PROFILES=worker docker compose build worker` on adama produced an image with psycopg 3.3.4 and the notify errors stopped.
- [ ] Beta deploy from this branch recreates the worker with a fresh image, no manual rebuild.
- [ ] Hosts without `compose_profiles` (roslin, ai-server-4080) still build/start web only.Reviewed-on: #19
2026-08-09 04:36:51 -07:00
westfarn b07e8cb4c3 Auto-start monica_site dj-queue worker on adama (#18)
Sync runner checkout / sync (push) Successful in 7s
## Summary
- Closes [#17](#17).
- Django deploy passes optional `host_apps.compose_profiles` as `COMPOSE_PROFILES` so compose profiles survive `up -d --remove-orphans`.
- Adama `monica_site` beta+prod set `compose_profiles: [worker]` (dj-queue singleton). Roslin / ai-server-4080 stay web-only.

## Why
Real campaign sends enqueue dj-queue tasks; without the worker they stay **Queued**. Test email worked because it is synchronous SMTP.

## Test plan
- [ ] Merge + deploy `monica_site` beta (and/or prod) via `deploy.sh`
- [ ] On **adama**, confirm worker container up for `monica_site_beta` / `monica_site_prod`
- [ ] On roslin / ai-server-4080, confirm **no** worker
- [ ] Portal: send campaign → recipient leaves Queued → SentReviewed-on: #18
2026-08-08 12:01:18 -07:00
16 changed files with 435 additions and 64 deletions
+192 -38
View File
@@ -10,6 +10,8 @@ flowchart TB
Control1["ai-server-4080\n(control node)"] Control1["ai-server-4080\n(control node)"]
Control1 -->|ansible-playbook site.yml| Adama Control1 -->|ansible-playbook site.yml| Adama
Control1 -->|ansible-playbook site.yml| Roslin Control1 -->|ansible-playbook site.yml| Roslin
Control1 -->|ansible-playbook site.yml| Starbuck
Control1 -->|ansible-playbook site.yml| Apollo
end end
subgraph cicd ["CI/CD (every merge to master)"] subgraph cicd ["CI/CD (every merge to master)"]
@@ -19,6 +21,8 @@ flowchart TB
Deploy --> AnsibleDeploy["ansible-playbook deploy-apps.yml"] Deploy --> AnsibleDeploy["ansible-playbook deploy-apps.yml"]
AnsibleDeploy --> Adama2["adama"] AnsibleDeploy --> Adama2["adama"]
AnsibleDeploy --> Roslin2["roslin"] AnsibleDeploy --> Roslin2["roslin"]
AnsibleDeploy --> Starbuck2["starbuck"]
AnsibleDeploy --> Apollo2["apollo"]
end end
``` ```
@@ -35,6 +39,8 @@ Both pipelines share the same inventory (`inventory/hosts.yml`).
|------|-----|------| |------|-----|------|
| adama | 10.0.0.77 | Ubuntu Server VM (Proxmox) — app host | | adama | 10.0.0.77 | Ubuntu Server VM (Proxmox) — app host |
| roslin | 10.0.0.176 | Ubuntu Server VM (Proxmox) — app host | | roslin | 10.0.0.176 | Ubuntu Server VM (Proxmox) — app host |
| starbuck | 10.0.0.44 | Ubuntu Server VM (Proxmox) — app host |
| apollo | 10.0.0.7 | Ubuntu Server VM (Proxmox) — app host |
| ai-server-4080 | 10.0.0.128 | Control node + Gitea act runner + Ollama + SearxNG + observability; also runs app replicas | | ai-server-4080 | 10.0.0.128 | Control node + Gitea act runner + Ollama + SearxNG + observability; also runs app replicas |
Hostname on this machine: `ryan-development-1` Hostname on this machine: `ryan-development-1`
@@ -52,8 +58,10 @@ server-infra/
│ ├── group_vars/ │ ├── group_vars/
│ │ └── all.yml # vars + app_catalog │ │ └── all.yml # vars + app_catalog
│ └── host_vars/ │ └── host_vars/
│ ├── adama.yml # host_apps (django + dta_webapp) │ ├── adama.yml # host_apps (django + static; monica worker)
│ ├── roslin.yml # host_apps (mirrors adama) │ ├── roslin.yml # host_apps (mirrors adama, no worker)
│ ├── starbuck.yml # host_apps (mirrors roslin)
│ ├── apollo.yml # host_apps (mirrors roslin)
│ └── ai-server-4080.yml # control node / act runner / SearxNG / observability │ └── ai-server-4080.yml # control node / act runner / SearxNG / observability
├── playbooks/ ├── playbooks/
│ ├── site.yml # Phase 1: provision │ ├── site.yml # Phase 1: provision
@@ -84,11 +92,15 @@ Ansible needs SSH + sudo on each target before playbooks work.
```bash ```bash
ssh-copy-id westfarn@10.0.0.77 ssh-copy-id westfarn@10.0.0.77
ssh-copy-id westfarn@10.0.0.176 ssh-copy-id westfarn@10.0.0.176
ssh-copy-id westfarn@10.0.0.44
ssh-copy-id westfarn@10.0.0.7
``` ```
3. Confirm passwordless SSH: 3. Confirm passwordless SSH:
```bash ```bash
ssh westfarn@10.0.0.77 ssh westfarn@10.0.0.77
ssh westfarn@10.0.0.176 ssh westfarn@10.0.0.176
ssh westfarn@10.0.0.44
ssh westfarn@10.0.0.7
``` ```
4. **First-time only** — grant passwordless sudo on each new host before the first 4. **First-time only** — grant passwordless sudo on each new host before the first
`provision.sh` run. Ubuntu 26.04 ships `sudo-rs` by default; Ansible's `provision.sh` run. Ubuntu 26.04 ships `sudo-rs` by default; Ansible's
@@ -142,6 +154,8 @@ New hosts need the one-time passwordless sudo bootstrap in
# Same for other hosts # Same for other hosts
./scripts/provision.sh roslin ./scripts/provision.sh roslin
./scripts/provision.sh starbuck
./scripts/provision.sh apollo
./scripts/provision.sh ai-server-4080 ./scripts/provision.sh ai-server-4080
``` ```
@@ -156,6 +170,8 @@ New hosts need the one-time passwordless sudo bootstrap in
```bash ```bash
./scripts/deploy.sh adama ./scripts/deploy.sh adama
./scripts/deploy.sh --check roslin ./scripts/deploy.sh --check roslin
./scripts/deploy.sh starbuck
./scripts/deploy.sh apollo
``` ```
Under the hood, scripts pass `--limit <hostname>` to `ansible-playbook`. Under the hood, scripts pass `--limit <hostname>` to `ansible-playbook`.
@@ -192,13 +208,21 @@ After Docker install, re-SSH so the `docker` group membership takes effect.
| App | Type | Hosts | Envs | Notes | | App | Type | Hosts | Envs | Notes |
|-----|------|-------|------|-------| |-----|------|-------|------|-------|
| `company_site` | django (docker) | adama + roslin (+ ai-server-4080) | prod | active/active behind NPM; beta port reserved | | `company_site` | django (docker) | all webservers | prod | active/active behind NPM; beta port reserved |
| `dta_service` | django (docker) | adama + roslin + ai-server-4080 | beta + prod | active/active behind NPM | | `dta_service` | django (docker) | all webservers | beta + prod | active/active behind NPM |
| `dta_webapp` | node/vite static | adama + roslin (+ ai-server-4080) | beta + prod | active/active; built to `/var/www/<env>.realpath.app/html`, served by web-static nginx | | `dta_webapp` | node/vite static | all webservers | beta + prod | active/active; built to `/var/www/<env>.realpath.app/html`, served by web-static nginx |
| `scha` | django (docker) | adama + roslin + ai-server-4080 | prod | active/active behind NPM; beta port reserved | | `dta_blog` | node-static (Python SSG) | all webservers | beta + prod | active/active; built to `/var/www/<env>.blog.realpath.app/html`; **own hosts** (`blog.realpath.app` / `beta.blog.realpath.app`), not `realpath.app/blog`; Tianji ids baked at build |
| `chat_web_app` | node-static (CRA) | adama + roslin + ai-server-4080 | beta + prod | active/active; built to `/var/www/<env>.chat.aimloperations/html`, served by web-static nginx | | `scha` | django (docker) | all webservers | prod | active/active behind NPM; beta port reserved |
| `chat_backend` | django (docker) | adama + roslin + ai-server-4080 | beta + prod | active/active behind NPM; Ollama `http://10.0.0.128:11434`; SearxNG `http://10.0.0.128:8088` (`SEARXNG_BASE_URL`) | | `chat_web_app` | node-static (CRA) | all webservers | beta + prod | active/active; built to `/var/www/<env>.chat.aimloperations/html`, served by web-static nginx |
| `monica_site` | django (docker) | adama + roslin + ai-server-4080 | beta + prod | active/active behind NPM; no bundled Postgres (like `scha`); dj-queue **worker singleton on adama** only (`compose --profile worker`); Ollama social drafting via `10.0.0.128:11434` | | `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`) |
| `monica_site` | django (docker) | all webservers | beta + prod | active/active behind NPM; no bundled Postgres (like `scha`); dj-queue **worker singleton on adama** only (`compose --profile worker`); Ollama social drafting via `10.0.0.128:11434` |
| `url_shortening_service` | django (docker) | all webservers | beta + prod | active/active behind NPM; no bundled Postgres; **no worker**. Two public hosts, same container: short domain (`GET /`, `GET /<code>` 302) and API host (`/api/links/`, Bearer required). |
| `college_craft` | django (docker) | all webservers | beta + prod | active/active behind NPM; no bundled Postgres (like `scha` / `monica_site`); dj-queue **worker singleton on adama** only (`compose --profile worker`); Ollama social drafting via `10.0.0.128:11434`; Nominatim `http://10.0.0.128:8089`; prod `SITE_UNDER_CONSTRUCTION=true` until launch |
| `print_forge` | django (docker) | all webservers | beta + prod | active/active behind NPM; no bundled Postgres (like `scha` / `monica_site` / `college_craft`); dj-queue **worker singleton on adama** only (`compose --profile worker`); Nominatim `http://10.0.0.128:8089`; prod `SITE_UNDER_CONSTRUCTION=true` until launch; prod NPM waits until launch |
| `abc_be` | django (docker) | all webservers | **beta only** | AI Benefits Coach API; no bundled Postgres; Ollama `http://10.0.0.128:11434`; shares DB `abc_be_beta` with `abc_worker` |
| `abc_worker` | django-type compose (FastAPI) | all webservers | **beta only** | same Postgres as `abc_be`; `migrate_cmd: true`; WS on host port 8018 |
| `abc_fe` | node-static (Vite) | all webservers | **beta only** | built to `/var/www/<env>.abc.aimloperations/html`; no prod `host_apps` row |
| `livekit` | django-type compose (LiveKit SFU) | all webservers | **beta only** | Docker like `abc_worker`. Host network `:7880`. **Do not NPM-balance** until Redis — pick one upstream (adama). Router DNAT UDP 3478 + 5000060000 and TCP 7881 to that host. Secrets `~/Documents/secrets/livekit/livekit_beta.env` |
Django apps use a **shared external Postgres** (via `DATABASE_URL` in each host's Django apps use a **shared external Postgres** (via `DATABASE_URL` in each host's
env file) so active/active replicas share one database. Beta and prod never share env file) so active/active replicas share one database. Beta and prod never share
@@ -209,9 +233,9 @@ a DB.
- `app_catalog` (`group_vars/all.yml`) — how each app is built (repo, type, compose file, migrate cmd). - `app_catalog` (`group_vars/all.yml`) — how each app is built (repo, type, compose file, migrate cmd).
- `host_apps` (`host_vars/<host>.yml`) — which app+env+port runs on that host. - `host_apps` (`host_vars/<host>.yml`) — which app+env+port runs on that host.
Optional `compose_profiles: [worker]` activates docker compose profiles on that Optional `compose_profiles: [worker]` activates docker compose profiles on that
host only (used for `monica_site` dj-queue singleton on adama). host only (used for `monica_site` / `college_craft` / `print_forge` dj-queue singleton on adama).
- Django app = one compose project per env: project name `<app>_<env>`, host port from `host_apps`. - Django app = one compose project per env: project name `<app>_<env>`, host port from `host_apps`.
Ports match across adama/roslin so NPM can balance `adama:PORT` + `roslin:PORT`. Ports match across app hosts so NPM can balance `adama:PORT` + `roslin:PORT` + `starbuck:PORT` + `apollo:PORT`.
### Ports ### Ports
@@ -221,20 +245,29 @@ future beta replica.
| App | beta | prod | Deployed on | | App | beta | prod | Deployed on |
|-----|------|------|-------------| |-----|------|------|-------------|
| company_site | 8010 (*not deployed*) | 8000 | adama, roslin, ai-server-4080 | | company_site | 8010 (*not deployed*) | 8000 | all webservers |
| dta_service | 8011 | 8001 | adama, roslin, ai-server-4080 | | dta_service | 8011 | 8001 | all webservers |
| scha | 8012 (*not deployed*) | 8002 | adama, roslin, ai-server-4080 | | scha | 8012 (*not deployed*) | 8002 | all webservers |
| chat_backend | 8013 | 8003 | adama, roslin, ai-server-4080 | | chat_backend | 8013 | 8003 | all webservers |
| monica_site | 8014 | 8004 | adama, roslin, ai-server-4080 | | monica_site | 8014 | 8004 | all webservers |
| dta_webapp (nginx) | 8081 | 8080 | adama, roslin, ai-server-4080 | | url_shortening_service | 8015 | 8005 | all webservers |
| chat_web_app (nginx) | 8083 | 8082 | adama, roslin, ai-server-4080 | | college_craft | 8016 | 8006 | all webservers |
| print_forge | **8019** | **8007** | all webservers |
| abc_be | **8017** | 8009 (*not deployed*) | all webservers |
| abc_worker | **8018** | 8008 (*not deployed*) | all webservers |
| 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 |
| livekit | **7880** | — | all webservers (signaling). UDP 3478 + 5000060000 + TCP 7881 via router DNAT to the NPM upstream host |
| SearxNG (LAN only) | — | **8088** | ai-server-4080 only (`searxng_stack`); not an NPM upstream | | 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): Host-local services on ai-server-4080 (not balanced by NPM):
| Service | Port | Notes | | Service | Port | Notes |
|---------|------|-------| |---------|------|-------|
| Ollama | 11434 | Not Ansible-managed today; GPU host | | Ollama | 11434 | Not Ansible-managed today; GPU host (`monica_site` / `college_craft` social drafting) |
| Nominatim | 8089 | Not Ansible-managed today; LAN address autocomplete for `college_craft` / `print_forge` |
| SearxNG | 8088 | `roles/searxng` (#10); JSON API for chat_backend grounded search | | SearxNG | 8088 | `roles/searxng` (#10); JSON API for chat_backend grounded search |
| Loki | 3100 | `roles/observability` | | Loki | 3100 | `roles/observability` |
| Prometheus | 9090 | `roles/observability` | | Prometheus | 9090 | `roles/observability` |
@@ -260,11 +293,14 @@ chat_backend secrets must use `SEARXNG_BASE_URL=http://10.0.0.128:8088`.
to host `{{ apps_env_dir }}` → git checkout at ref → copy `.env` into checkout → to host `{{ apps_env_dir }}` → git checkout at ref → copy `.env` into checkout →
`docker compose build` → `up -d` (with `COMPOSE_PROFILES` from optional `docker compose build` → `up -d` (with `COMPOSE_PROFILES` from optional
`host_apps.compose_profiles`) → migrate (run once, shared DB). `host_apps.compose_profiles`) → migrate (run once, shared DB).
**`monica_site` worker:** adama `host_apps` sets `compose_profiles: [worker]` so **`monica_site` / `college_craft` / `print_forge` worker:** adama `host_apps` sets
deploy starts dj-queue with web. Other hosts omit profiles (web only). `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:<env>` - **node-static**: git checkout at ref → `npm ci` → `npm run build:<env>`
(writes to the app's `webroot_pattern`, e.g. `/var/www/{env}.realpath.app/html` (writes to the app's `webroot_pattern`, e.g. `/var/www/{env}.realpath.app/html`,
or `/var/www/{env}.chat.aimloperations/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 - **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`, on their ports (from `host_apps`); NPM balances across hosts. Before `compose up`,
removes any container currently publishing those host ports (`docker ps --filter removes any container currently publishing those host ports (`docker ps --filter
@@ -277,18 +313,27 @@ point each domain at the backend(s):
- Single host: standard Proxy Host → `adama:PORT`. - Single host: standard Proxy Host → `adama:PORT`.
- Active/active: jc21 NPM's UI Proxy Host is single-target. To balance - Active/active: jc21 NPM's UI Proxy Host is single-target. To balance
adama+roslin you need the **Advanced** tab with a custom `upstream {}` block app hosts you need the **Advanced** tab with a custom `upstream {}` block
(or a real LB). Confirm this before relying on active/active. (or a real LB). Confirm this before relying on active/active.
| App | Domains | Backends | | App | Domains | Backends |
|-----|---------|----------| |-----|---------|----------|
| company_site | aimloperations.com (+ www) | `adama:8000` + `roslin:8000` | | 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 / ai-server-4080 | | 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 | | dta_webapp | (see DTA NPM hosts) | `adama:8080` / `8081` + same on roslin / starbuck / apollo |
| scha | `schawheaton.aimloperations.com`, `schawheaton.com` (+ www) | `adama:8002` + `roslin:8002` (+ `ai-server-4080:8002`) | | 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 |
| chat_web_app | `chat.aimloperations.com` (+ www); `beta.chat.aimloperations.com` | `adama:8082` / `8083` + same on roslin / ai-server-4080 | | scha | `schawheaton.aimloperations.com`, `schawheaton.com` (+ www) | `adama:8002` + `roslin:8002` + `starbuck:8002` + `apollo:8002` (+ `ai-server-4080:8002`) |
| chat_backend | `chatbackend.aimloperations.com`; `beta.chatbackend.aimloperations.com` | `adama:8003` / `8013` + same on roslin / ai-server-4080 | | chat_web_app | `chat.aimloperations.com` (+ www); `beta.chat.aimloperations.com` | `adama:8082` / `8083` + same on roslin / starbuck / apollo / ai-server-4080 |
| monica_site | `mkdrealtor.com` (+ www); `monica-preview.aimloperations.com` (beta) | `adama:8004` / `8014` + same on roslin / ai-server-4080 | | chat_backend | `chatbackend.aimloperations.com`; `beta.chatbackend.aimloperations.com` | `adama:8003` / `8013` + same on roslin / starbuck / apollo / ai-server-4080 |
| monica_site | `mkdrealtor.com` (+ www); `monica-preview.aimloperations.com` (beta) | `adama:8004` / `8014` + same on roslin / starbuck / apollo / ai-server-4080 |
| url_shortening_service (short) | `aiml.pw` and/or `cidinn.li` (pick when DNS is ready); `short-beta.aimloperations.com` (beta). Proxy `/` + `/[a-z0-9]{4,8}` only — 404 `/api/`, `/admin/`, `/debug/` | `adama:8005` / `8015` + same on roslin / starbuck / apollo / ai-server-4080 |
| url_shortening_service (API) | `shortener.aimloperations.com`; `shortener-beta.aimloperations.com` (beta). Proxy `/api/` only — 404 `/admin/` | same ports as short host (one container) |
| college_craft | `collegecraft.com` (+ www); `college-craft-preview.aimloperations.com` (beta) | `adama:8006` / `8016` + same on roslin / starbuck / apollo / ai-server-4080 |
| print_forge | `printforgeprints.com` (+ www) — **do not NPM-route prod until launch**; `print-forge-preview.aimloperations.com` (beta) | `adama:8007` / `8019` + same on roslin / starbuck / apollo / ai-server-4080 |
| abc_fe | `beta.abc.aimloperations.com` (beta only) | `adama:8085` + same on roslin / starbuck / apollo / ai-server-4080 |
| abc_be | `beta.abc.be.aimloperations.com` (beta only) | `adama:8017` + same on roslin / starbuck / apollo / ai-server-4080 |
| abc_worker | `beta.abc.worker.aimloperations.com` (beta only; HTTP + WebSocket upgrade) | `adama:8018` + same on roslin / starbuck / apollo / ai-server-4080 |
| livekit | `LIVEKIT_DOMAIN` (e.g. `livekit.aimloperations.com`) — NPM Proxy Host, WebSocket, **single upstream** | `adama:7880` (do not balance until Redis) |
### Required changes IN each app repo (owned separately) ### Required changes IN each app repo (owned separately)
@@ -300,6 +345,9 @@ point each domain at the backend(s):
(keep the test/docker jobs). (keep the test/docker jobs).
- [ ] `dta_webapp`: `npm run build:beta` / `build:prod` output to - [ ] `dta_webapp`: `npm run build:beta` / `build:prod` output to
`/var/www/beta.realpath.app/html` / `/var/www/prod.realpath.app/html`. `/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 <env>` copies `dist/`). Default branch is `main`.
- [ ] `chat_web_app`: `npm run build:beta` / `build:prod` output to - [ ] `chat_web_app`: `npm run build:beta` / `build:prod` output to
`/var/www/beta.chat.aimloperations/html` / `/var/www/prod.chat.aimloperations/html`. `/var/www/beta.chat.aimloperations/html` / `/var/www/prod.chat.aimloperations/html`.
@@ -323,11 +371,110 @@ do not).
| chat_backend | beta | `chat_backend_beta` | `postgres://westfarn:<pw>@10.0.0.230:5432/chat_backend_beta` | | chat_backend | beta | `chat_backend_beta` | `postgres://westfarn:<pw>@10.0.0.230:5432/chat_backend_beta` |
| monica_site | prod | `monica_site` | `postgres://westfarn:<pw>@10.0.0.230:5432/monica_site` | | monica_site | prod | `monica_site` | `postgres://westfarn:<pw>@10.0.0.230:5432/monica_site` |
| monica_site | beta | `monica_site_beta` | `postgres://westfarn:<pw>@10.0.0.230:5432/monica_site_beta` | | monica_site | beta | `monica_site_beta` | `postgres://westfarn:<pw>@10.0.0.230:5432/monica_site_beta` |
| url_shortening_service | prod | `url_shortener` | `postgres://westfarn:<pw>@10.0.0.230:5432/url_shortener` |
| url_shortening_service | beta | `url_shortener_beta` | `postgres://westfarn:<pw>@10.0.0.230:5432/url_shortener_beta` |
| college_craft | prod | `college_craft` | `postgres://westfarn:<pw>@10.0.0.230:5432/college_craft` |
| college_craft | beta | `college_craft_beta` | `postgres://westfarn:<pw>@10.0.0.230:5432/college_craft_beta` |
| print_forge | prod | `print_forge` | `postgres://westfarn:<pw>@10.0.0.230:5432/print_forge` |
| print_forge | beta | `print_forge_beta` | `postgres://westfarn:<pw>@10.0.0.230:5432/print_forge_beta` |
| abc_be | beta | `abc_be_beta` | `postgres://westfarn:<pw>@10.0.0.230:5432/abc_be_beta` |
| abc_worker | beta | *(same `abc_be_beta`)* | worker uses `DB_*` pointing at `abc_be_beta` — do **not** create a second DB |
Server prereqs on 10.0.0.230: create each DB + grant `westfarn`; Server prereqs on 10.0.0.230: create each DB + grant `westfarn`;
`listen_addresses` covers LAN; `pg_hba.conf` allows `10.0.0.0/24`; firewall opens `listen_addresses` covers LAN; `pg_hba.conf` allows `10.0.0.0/24`; firewall opens
5432 to `10.0.0.0/24` only. 5432 to `10.0.0.0/24` only.
`url_shortening_service` extra env (control-node secrets, not in git):
`SHORT_PUBLIC_HOSTS` / `SHORT_API_HOSTS` / `SHORT_ADMIN_HOSTS` (admin = `localhost,127.0.0.1` only),
`SHORTENER_API_TOKENS` (`monica:<token>`), `SHORT_ALLOWED_HOSTS`, `CLICK_IP_PEPPER`
(distinct from `DJANGO_SECRET_KEY`). `DJANGO_DEBUG=false` for prod and beta.
Caller `monica_site` uses `SHORTENER_BASE_URL=https://<api-host>` and Bearer mint;
do not mint via the short hostname.
`college_craft` extra env (control-node secrets, not in git):
`~/Documents/secrets/college_craft/college_craft_prod.env` and
`college_craft_beta.env`. Template: app repo `.env.prod.example`.
`DJANGO_ENV` / `DJANGO_ALLOWED_HOSTS` / `DATABASE_URL` / `WEB_PORT` /
`PUBLIC_SITE_URL` differ per env (prod `collegecraft.com` / `:8006`;
beta `college-craft-preview.aimloperations.com` / `:8016`).
`SITE_UNDER_CONSTRUCTION=true` on prod (holding page until launch), `false` on beta.
`FEATURE_BLOG=true`; other `FEATURE_*` stay false until purchased. Prod also needs
`TIANJI_WEBSITE_ID` + reCAPTCHA keys. If email/SMS or direct mail is turned on later,
add a `college_craft:<token>` entry to `SHORTENER_API_TOKENS` on `url_shortening_service`.
App-repo companion (`college_craft`, not this repo): deploy workflows still call
`--app client_site` (template leftover). They must become `--app college_craft`
before CI deploy will hit this catalog entry. Default branch is `master` (not `main`).
`print_forge` extra env (control-node secrets, not in git):
`~/Documents/secrets/print_forge/print_forge_prod.env` and
`print_forge_beta.env`. Template: app repo `.env.prod.example`.
`DJANGO_ENV` / `DJANGO_ALLOWED_HOSTS` / `DATABASE_URL` / `WEB_PORT` /
`PUBLIC_SITE_URL` differ per env (prod `printforgeprints.com` / `:8007`;
beta `print-forge-preview.aimloperations.com` / `:8019`).
`SITE_UNDER_CONSTRUCTION=true` on prod (holding page until launch), `false` on beta.
Purchased flags: `FEATURE_EMAIL_SMS`, `FEATURE_PAYMENTS`, `FEATURE_SHOP`,
`FEATURE_SHIPPING`. Other `FEATURE_*` stay false. Add a `print_forge:<token>`
entry to `SHORTENER_API_TOKENS` on `url_shortening_service` (beta token first).
`SHORTENER_BASE_URL` is prod shortener on prod, `https://shortener-beta.aimloperations.com`
on beta. Default branch is `master`.
Companion app ticket: [print_forge#1](https://git.aimloperations.com/ai_ml_operations/print_forge/issues/1)
([#27](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/27)).
App Gitea workflow deploys beta on push to `master` (`--app print_forge`).
`abc_be` / `abc_worker` / `abc_fe` (GIS org, not this repo) — **beta only** ([#26](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/26)):
Control-node secrets (never git, mode `600`):
```text
~/Documents/secrets/abc_be/abc_be_beta.env
~/Documents/secrets/abc_worker/abc_worker_beta.env
```
Templates: `GIS/abc_be` `.env.beta.example`, `GIS/abc_worker` `.env.beta.example`.
`abc_fe` has no secret file — public `VITE_*` URLs live in committed `.env.beta`.
Create Postgres DB `abc_be_beta` and grant `westfarn` before first deploy.
NPM: `beta.abc.aimloperations.com` → `:8085`, `beta.abc.be.aimloperations.com` → `:8017`,
`beta.abc.worker.aimloperations.com` → `:8018` (enable WebSocket). No prod ABC
`host_apps` rows; ports **8009** / 8008 / 8084 reserved (`print_forge` took prod **8007**).
Companion workflows: [abc_be#22](https://git.aimloperations.com/GIS/abc_be/issues/22),
[abc_worker#27](https://git.aimloperations.com/GIS/abc_worker/issues/27),
[abc_fe#27](https://git.aimloperations.com/GIS/abc_fe/issues/27).
`livekit` (GIS org, SFU on webservers — [abc_worker#14](https://git.aimloperations.com/GIS/abc_worker/issues/14)[#17](https://git.aimloperations.com/GIS/abc_worker/issues/17)):
Control-node secret (never git, mode `600`):
```text
~/Documents/secrets/livekit/livekit_beta.env
```
Template: `GIS/livekit` `.env.beta.example`. Same `LIVEKIT_API_KEY` /
`LIVEKIT_API_SECRET` pair as `abc_be` / `abc_worker`; worker `LIVEKIT_URL=wss://<LIVEKIT_DOMAIN>`.
Deploy: `./scripts/deploy.sh --app livekit --env beta` (all webservers, like
`abc_worker`). NPM: `LIVEKIT_DOMAIN` → **one** host `:7880` with WebSocket
(adama). Router DNAT UDP 3478 + 5000060000 and TCP 7881 to that same host.
Do not active/active-balance LiveKit until Redis is in the compose.
`dta_blog` ([#29](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/29)) —
Python SSG, **no secrets file**. `npm run build:<env>` writes
`/var/www/<env>.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) ### One-time host bootstrap (per target)
- [x] Gitea SSH key: the `gitea-key` role (in `site.yml`) generates a key per - [x] Gitea SSH key: the `gitea-key` role (in `site.yml`) generates a key per
@@ -338,7 +485,7 @@ Server prereqs on 10.0.0.230: create each DB + grant `westfarn`;
(default `~/Documents/secrets/<app>/<app>_<env>.env`) with `DATABASE_URL` (default `~/Documents/secrets/<app>/<app>_<env>.env`) with `DATABASE_URL`
(see table), `DJANGO_ENV`, `DJANGO_SECRET_KEY`, `WEB_PORT` (matching the port (see table), `DJANGO_ENV`, `DJANGO_SECRET_KEY`, `WEB_PORT` (matching the port
table). Deploy pushes these to `/opt/apps/env/<app>_<env>.env` (mode 600) on table). Deploy pushes these to `/opt/apps/env/<app>_<env>.env` (mode 600) on
adama + roslin. Never committed to git. adama + roslin + starbuck + apollo. Never committed to git.
- [x] Node.js/npm/npx for the `dta_webapp` build — installed by the `nodejs` - [x] Node.js/npm/npx for the `dta_webapp` build — installed by the `nodejs`
role in `site.yml` (NodeSource, `node_major` default 20). role in `site.yml` (NodeSource, `node_major` default 20).
@@ -347,7 +494,7 @@ Server prereqs on 10.0.0.230: create each DB + grant `westfarn`;
**Recommended:** Single self-hosted runner on ai-server-4080. **Recommended:** Single self-hosted runner on ai-server-4080.
- One orchestration point. - One orchestration point.
- App hosts (adama/roslin) run the workloads; no runner needed on them for deploy fan-out. - App hosts (adama/roslin/starbuck/apollo) run the workloads; no runner needed on them for deploy fan-out.
- Runner needs: Ansible, this repo checked out, SSH key to all hosts, vault password (later). - Runner needs: Ansible, this repo checked out, SSH key to all hosts, vault password (later).
### Runner requirements on ai-server-4080 ### Runner requirements on ai-server-4080
@@ -356,7 +503,7 @@ Server prereqs on 10.0.0.230: create each DB + grant `westfarn`;
|-------------|-----| |-------------|-----|
| Ansible | Run `deploy-apps.yml` | | Ansible | Run `deploy-apps.yml` |
| `server-infra` checkout | Playbooks + inventory | | `server-infra` checkout | Playbooks + inventory |
| SSH key to adama + roslin | Deploy fan-out | | SSH key to app hosts | Deploy fan-out |
On every push or merged PR to `master`, `.gitea/workflows/sync-checkout.yml` On every push or merged PR to `master`, `.gitea/workflows/sync-checkout.yml`
fast-forward pulls this repo at `~/Documents/repos/server-infra` on the Act fast-forward pulls this repo at `~/Documents/repos/server-infra` on the Act
@@ -387,8 +534,8 @@ Store vault password for CI in a file readable only by the Act runner (e.g. `~/.
| # | Task | Status | | # | Task | Status |
|---|------|--------| |---|------|--------|
| 1 | Create `server-infra` repo | Done | | 1 | Create `server-infra` repo | Done |
| 2 | Inventory with all 3 hosts | Done | | 2 | Inventory with all 5 hosts | Done ([#20](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/20)) |
| 3 | Bootstrap SSH to adama + roslin | Manual | | 3 | Bootstrap SSH to app hosts | Manual |
| 4 | `site.yml` → common, ufw, docker | Done | | 4 | `site.yml` → common, ufw, docker | Done |
| 5 | Verify `ansible webservers -m ping` | Manual | | 5 | Verify `ansible webservers -m ping` | Manual |
| 6 | Test on single server: `./scripts/provision.sh adama` | Manual | | 6 | Test on single server: `./scripts/provision.sh adama` | Manual |
@@ -401,13 +548,20 @@ Store vault password for CI in a file readable only by the Act runner (e.g. `~/.
| 10b | Register + deploy `chat_web_app` (node-static, ports 8082/8083) | Done (prod); beta ([#7](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/7), [chat_web_app#35](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/35)) | | 10b | Register + deploy `chat_web_app` (node-static, ports 8082/8083) | Done (prod); beta ([#7](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/7), [chat_web_app#35](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/35)) |
| 10c | Register + deploy `chat_backend` (django, ports 8003/8013) | Done (prod); beta ([#7](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/7), [chat_backend#26](https://git.aimloperations.com/ai_ml_operations/chat_backend/issues/26)) | | 10c | Register + deploy `chat_backend` (django, ports 8003/8013) | Done (prod); beta ([#7](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/7), [chat_backend#26](https://git.aimloperations.com/ai_ml_operations/chat_backend/issues/26)) |
| 10d | Register + deploy `monica_site` (django, ports 8004/8014) | Done ([#14](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/14)) | | 10d | Register + deploy `monica_site` (django, ports 8004/8014) | Done ([#14](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/14)) |
| 10e | Auto-start `monica_site` dj-queue worker on adama (`compose_profiles`) | In progress ([#17](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/17)) | | 10e | Auto-start `monica_site` dj-queue worker on adama (`compose_profiles`) | Done ([#17](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/17)) |
| 10f | Add starbuck + apollo as app hosts (same workloads as roslin) | Done ([#20](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/20)) |
| 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) | Done ([#27](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/27)) |
| 10k | Register + deploy `dta_blog` (node-static, ports 8086/8087) | Done ([#29](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/29)) |
| 10l | Register GIS LiveKit SFU (`abc_worker`-style docker on webservers, port 7880) | This PR |
| 11 | Gitea container registry (optional) | Future | | 11 | Gitea container registry (optional) | Future |
## Open Decisions ## Open Decisions
1. **Deploy user** — `westfarn` vs dedicated `deploy` for CI. 1. **Deploy user** — `westfarn` vs dedicated `deploy` for CI.
2. **NPM load balancing** — confirm jc21 NPM can express adama+roslin upstreams (Advanced tab), else active/active is just two independent instances. 2. **NPM load balancing** — confirm jc21 NPM can express all app-host upstreams (Advanced tab), else active/active is just independent instances.
3. **Secrets** — Ansible Vault vs per-host env files (currently per-host `/opt/apps/env/*.env`). 3. **Secrets** — Ansible Vault vs per-host env files (currently per-host `/opt/apps/env/*.env`).
## Adding a New VM ## Adding a New VM
+4
View File
@@ -11,6 +11,8 @@ ansible-galaxy collection install -r requirements.yml
# Bootstrap SSH key to each host (one-time, before Ansible) # Bootstrap SSH key to each host (one-time, before Ansible)
ssh-copy-id westfarn@10.0.0.77 ssh-copy-id westfarn@10.0.0.77
ssh-copy-id westfarn@10.0.0.176 ssh-copy-id westfarn@10.0.0.176
ssh-copy-id westfarn@10.0.0.44
ssh-copy-id westfarn@10.0.0.7
# First-time only: passwordless sudo on each new host (before first provision) # First-time only: passwordless sudo on each new host (before first provision)
ssh -t westfarn@10.0.0.176 # repeat for each host IP ssh -t westfarn@10.0.0.176 # repeat for each host IP
@@ -46,4 +48,6 @@ the central **Loki / Prometheus / Grafana** stack (`observability_stack: true`).
|------|-----|------| |------|-----|------|
| adama | 10.0.0.77 | app host | | adama | 10.0.0.77 | app host |
| roslin | 10.0.0.176 | app host | | roslin | 10.0.0.176 | app host |
| starbuck | 10.0.0.44 | app host |
| apollo | 10.0.0.7 | app host |
| ai-server-4080 | 10.0.0.128 | control node + act runner | | ai-server-4080 | 10.0.0.128 | control node + act runner |
+2 -2
View File
@@ -45,7 +45,7 @@ first; use text search (`|=`, `|~`) on logs second.
| Label | Meaning | Examples | | Label | Meaning | Examples |
|-------|---------|----------| |-------|---------|----------|
| `host` | Inventory hostname | `adama`, `roslin`, `ai-server-4080` | | `host` | Inventory hostname | `adama`, `roslin`, `starbuck`, `apollo`, `ai-server-4080` |
| `env` | Deploy environment | `beta`, `prod`, `host` (journal), `infra` (stack containers) | | `env` | Deploy environment | `beta`, `prod`, `host` (journal), `infra` (stack containers) |
| `app` | App / service name | `company_site`, `dta_service`, `dta_webapp`, `system` | | `app` | App / service name | `company_site`, `dta_service`, `dta_webapp`, `system` |
| `job` | Collector | `docker`, `systemd` | | `job` | Collector | `docker`, `systemd` |
@@ -56,7 +56,7 @@ first; use text search (`|=`, `|~`) on logs second.
| Label | Meaning | Examples | | Label | Meaning | Examples |
|-------|---------|----------| |-------|---------|----------|
| `host` | Inventory hostname (stamped by Alloy) | `adama`, `roslin`, `ai-server-4080` | | `host` | Inventory hostname (stamped by Alloy) | `adama`, `roslin`, `starbuck`, `apollo`, `ai-server-4080` |
| `job` | Scrape job | `node` (host), `cadvisor` (containers) | | `job` | Scrape job | `node` (host), `cadvisor` (containers) |
| `env` / `app` | Parsed from Compose project `<app>_<env>` | `prod` / `dta_service` | | `env` / `app` | Parsed from Compose project `<app>_<env>` | `prod` / `dta_service` |
| `name` | Container name (cAdvisor) | `company_site_prod-web-1` | | `name` | Container name (cAdvisor) | `company_site_prod-web-1` |
+17 -7
View File
@@ -10,6 +10,8 @@ flowchart LR
subgraph hosts ["All webservers"] subgraph hosts ["All webservers"]
A["adama\nAlloy"] A["adama\nAlloy"]
R["roslin\nAlloy"] R["roslin\nAlloy"]
S["starbuck\nAlloy"]
Ap["apollo\nAlloy"]
C["ai-server-4080\nAlloy"] C["ai-server-4080\nAlloy"]
end end
@@ -24,9 +26,13 @@ flowchart LR
A -->|logs| L A -->|logs| L
R -->|logs| L R -->|logs| L
S -->|logs| L
Ap -->|logs| L
C -->|logs| L C -->|logs| L
A -->|metrics| P A -->|metrics| P
R -->|metrics| P R -->|metrics| P
S -->|metrics| P
Ap -->|metrics| P
C -->|metrics| P C -->|metrics| P
L --> G L --> G
P --> G P --> G
@@ -36,7 +42,7 @@ flowchart LR
| Piece | Where | Role | | Piece | Where | Role |
|-------|--------|------| |-------|--------|------|
| **Alloy** | every host (`adama`, `roslin`, `ai-server-4080`) | Ship journald + Docker **logs** to Loki; scrape host + container **metrics** → Prometheus | | **Alloy** | every host (`adama`, `roslin`, `starbuck`, `apollo`, `ai-server-4080`) | Ship journald + Docker **logs** to Loki; scrape host + container **metrics** → Prometheus |
| **Loki** | `ai-server-4080` only | Store and index logs | | **Loki** | `ai-server-4080` only | Store and index logs |
| **Prometheus** | `ai-server-4080` only | Store metrics (CPU, RAM, disk, container health) | | **Prometheus** | `ai-server-4080` only | Store metrics (CPU, RAM, disk, container health) |
| **Grafana** | `ai-server-4080` only | Explore logs/metrics, dashboards, alerts | | **Grafana** | `ai-server-4080` only | Explore logs/metrics, dashboards, alerts |
@@ -182,7 +188,7 @@ services:
user: "0:0" user: "0:0"
command: -config.file=/etc/loki/loki-config.yml command: -config.file=/etc/loki/loki-config.yml
ports: ports:
# Bind to all interfaces so Alloy on adama/roslin can push. # Bind to all interfaces so Alloy on app hosts can push.
# Firewall (UFW) should restrict who can connect — see 1.6. # Firewall (UFW) should restrict who can connect — see 1.6.
- "3100:3100" - "3100:3100"
volumes: volumes:
@@ -376,7 +382,7 @@ Alloy runs on **every** host in `webservers`. It:
| Label | Source | Example values | | Label | Source | Example values |
|-------|--------|----------------| |-------|--------|----------------|
| `host` | Ansible inventory hostname | `adama`, `roslin`, `ai-server-4080` | | `host` | Ansible inventory hostname | `adama`, `roslin`, `starbuck`, `apollo`, `ai-server-4080` |
| `job` | collector name | `systemd`, `docker` | | `job` | collector name | `systemd`, `docker` |
| `env` | Docker Compose project suffix | `beta`, `prod`, `host`, `infra` | | `env` | Docker Compose project suffix | `beta`, `prod`, `host`, `infra` |
| `app` | Compose project prefix | `company_site`, `dta_service`, `dta_webapp`, … | | `app` | Compose project prefix | `company_site`, `dta_service`, `dta_webapp`, … |
@@ -387,7 +393,7 @@ Alloy runs on **every** host in `webservers`. It:
| Label | Source | Example values | | Label | Source | Example values |
|-------|--------|----------------| |-------|--------|----------------|
| `host` | Added by Alloy relabel | `adama`, `roslin`, `ai-server-4080` | | `host` | Added by Alloy relabel | `adama`, `roslin`, `starbuck`, `apollo`, `ai-server-4080` |
| `job` | scrape job name | `node`, `cadvisor` | | `job` | scrape job name | `node`, `cadvisor` |
| `name` | container name (cAdvisor) | `company_site_prod-web-1` | | `name` | container name (cAdvisor) | `company_site_prod-web-1` |
| `container_label_com_docker_compose_project` | Compose project | `dta_service_prod` | | `container_label_com_docker_compose_project` | Compose project | `dta_service_prod` |
@@ -407,7 +413,7 @@ container_memory_usage_bytes{host="adama", env="prod", app="dta_service"}
### 3.1 Install Alloy (manual — one host) ### 3.1 Install Alloy (manual — one host)
Repeat on `adama`, `roslin`, and `ai-server-4080`. Example for **adama**: Repeat on `adama`, `roslin`, `starbuck`, `apollo`, and `ai-server-4080`. Example for **adama**:
```bash ```bash
sudo mkdir -p /opt/apps/observability/alloy sudo mkdir -p /opt/apps/observability/alloy
@@ -415,7 +421,7 @@ sudo chown -R westfarn:westfarn /opt/apps/observability
``` ```
Create `/opt/apps/observability/alloy/config.alloy`. **Change every Create `/opt/apps/observability/alloy/config.alloy`. **Change every
`host = "adama"`** on each machine (`adama` / `roslin` / `ai-server-4080`): `host = "adama"`** on each machine (`adama` / `roslin` / `starbuck` / `apollo` / `ai-server-4080`):
```river ```river
// Grafana Alloy — logs → Loki, metrics → Prometheus. // Grafana Alloy — logs → Loki, metrics → Prometheus.
@@ -705,6 +711,8 @@ Healthy Alloy logs mention connecting / sending without repeated
```logql ```logql
{host="adama"} {host="adama"}
{host="roslin"} {host="roslin"}
{host="starbuck"}
{host="apollo"}
{host="ai-server-4080"} {host="ai-server-4080"}
``` ```
@@ -816,6 +824,8 @@ observability_stack: true
./scripts/provision.sh ai-server-4080 ./scripts/provision.sh ai-server-4080
./scripts/provision.sh adama ./scripts/provision.sh adama
./scripts/provision.sh roslin ./scripts/provision.sh roslin
./scripts/provision.sh starbuck
./scripts/provision.sh apollo
# or all (site.yml orders stack before Alloy): # or all (site.yml orders stack before Alloy):
./scripts/provision.sh ./scripts/provision.sh
``` ```
@@ -916,7 +926,7 @@ du -sh /opt/apps/observability/loki/data \
- [ ] `GF_SERVER_ROOT_URL` matches public URL - [ ] `GF_SERVER_ROOT_URL` matches public URL
- [ ] Both Loki and Prometheus datasources green in Grafana - [ ] Both Loki and Prometheus datasources green in Grafana
### Alloy (each of adama, roslin, ai-server-4080) ### Alloy (each of adama, roslin, starbuck, apollo, ai-server-4080)
- [ ] `config.alloy` has correct `host = "..."` (or Ansible `inventory_hostname`) - [ ] `config.alloy` has correct `host = "..."` (or Ansible `inventory_hostname`)
- [ ] Alloy container running privileged with host `/proc` `/sys` mounts - [ ] Alloy container running privileged with host `/proc` `/sys` mounts
+72
View File
@@ -12,6 +12,15 @@ ufw_allowed_tcp_ports:
- 80 - 80
- 443 - 443
# LiveKit on webservers. Signaling stays LAN (NPM → :7880). UDP must be
# reachable from the internet via router DNAT to the NPM upstream host.
ufw_lan_tcp_ports:
- 7880
- 7881
ufw_extra_udp_ports:
- 3478
- "50000:60000"
# Docker # Docker
docker_users: docker_users:
- "{{ admin_user }}" - "{{ admin_user }}"
@@ -80,6 +89,15 @@ app_catalog:
webroot_pattern: "/var/www/{env}.realpath.app/html" webroot_pattern: "/var/www/{env}.realpath.app/html"
# deploy runs `npm ci` then `npm run build:<env>`; that script writes to # 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. # {{ 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: scha:
type: django type: django
repo: "{{ git_base_url }}/ai_ml_operations/scha.git" repo: "{{ git_base_url }}/ai_ml_operations/scha.git"
@@ -112,6 +130,60 @@ app_catalog:
migrate_cmd: "uv run python manage.py migrate --noinput" migrate_cmd: "uv run python manage.py migrate --noinput"
# dj-queue worker = compose profile `worker` (singleton). Deploy starts it # dj-queue worker = compose profile `worker` (singleton). Deploy starts it
# via host_apps.compose_profiles on adama only (see host_vars/adama.yml). # 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: master
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: master
compose_file: docker-compose.prod.yml
web_service: web
# FastAPI — no Django migrations. `true` keeps the django deploy path happy.
migrate_cmd: "true"
abc_fe:
type: node-static
repo: "{{ git_base_url }}/GIS/abc_fe.git"
default_branch: master
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 # Deploy filter vars. CI passes these; manual runs may leave them undefined
# to (re)deploy every app listed in the host's host_apps. # to (re)deploy every app listed in the host's host_apps.
+17 -4
View File
@@ -1,7 +1,8 @@
--- ---
# Django services run active/active here and on roslin (shared external DB). # Django services run active/active here and on roslin/starbuck/apollo
# dta_webapp static also runs active/active (built into /var/www, served by # (shared external DB). dta_webapp static also runs active/active (built into
# the web-static nginx container). Ports MUST match roslin so NPM can balance. # /var/www, served by the web-static nginx container). Ports MUST match the
# other app hosts so NPM can balance.
# Each entry is one workload: django -> compose project <name>_<env> on port; # Each entry is one workload: django -> compose project <name>_<env> on port;
# node-static -> /var/www/<env>_dta_webapp served on port. # node-static -> /var/www/<env>_dta_webapp served on port.
host_apps: host_apps:
@@ -10,12 +11,24 @@ host_apps:
- { name: dta_service, env: beta, port: 8011 } - { name: dta_service, env: beta, port: 8011 }
- { name: dta_webapp, env: prod, port: 8080 } - { name: dta_webapp, env: prod, port: 8080 }
- { name: dta_webapp, env: beta, port: 8081 } - { 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 } - { name: scha, env: prod, port: 8002 }
# optional: - { name: scha, env: beta, port: 8012 } # optional: - { name: scha, env: beta, port: 8012 }
- { name: chat_web_app, env: prod, port: 8082 } - { name: chat_web_app, env: prod, port: 8082 }
- { name: chat_web_app, env: beta, port: 8083 } - { name: chat_web_app, env: beta, port: 8083 }
- { name: chat_backend, env: prod, port: 8003 } - { name: chat_backend, env: prod, port: 8003 }
- { name: chat_backend, env: beta, port: 8013 } - { name: chat_backend, env: beta, port: 8013 }
# compose_profiles: worker → dj-queue singleton (not on roslin / ai-server-4080). # compose_profiles: worker → dj-queue singleton (not on other app hosts).
- { name: monica_site, env: prod, port: 8004, compose_profiles: [worker] } - { name: monica_site, env: prod, port: 8004, compose_profiles: [worker] }
- { name: monica_site, env: beta, port: 8014, compose_profiles: [worker] } - { name: monica_site, env: beta, port: 8014, compose_profiles: [worker] }
- { name: url_shortening_service, env: prod, port: 8005 }
- { name: url_shortening_service, env: beta, port: 8015 }
- { name: college_craft, env: prod, port: 8006, compose_profiles: [worker] }
- { name: college_craft, env: beta, port: 8016, compose_profiles: [worker] }
- { name: print_forge, env: prod, port: 8007, compose_profiles: [worker] }
- { name: print_forge, env: beta, port: 8019, compose_profiles: [worker] }
- { name: abc_be, env: beta, port: 8017 }
- { name: abc_worker, env: beta, port: 8018 }
- { name: abc_fe, env: beta, port: 8085 }
- { name: livekit, env: beta, port: 7880 }
+15 -3
View File
@@ -22,11 +22,13 @@ searxng_stack: true
gitea_key_path: "/home/{{ admin_user }}/.ssh/gitea_deploy" gitea_key_path: "/home/{{ admin_user }}/.ssh/gitea_deploy"
# company_site + dta_webapp + dta_service for side testing and active/active. # company_site + dta_webapp + dta_service for side testing and active/active.
# Ports MUST match adama/roslin so NPM can balance all three upstreams. # Ports MUST match adama/roslin/starbuck/apollo so NPM can balance all upstreams.
host_apps: host_apps:
- { name: company_site, env: prod, port: 8000 } - { name: company_site, env: prod, port: 8000 }
- { name: dta_webapp, env: prod, port: 8080 } - { name: dta_webapp, env: prod, port: 8080 }
- { name: dta_webapp, env: beta, port: 8081 } - { 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: prod, port: 8001 }
- { name: dta_service, env: beta, port: 8011 } - { name: dta_service, env: beta, port: 8011 }
- { name: scha, env: prod, port: 8002 } - { name: scha, env: prod, port: 8002 }
@@ -34,6 +36,16 @@ host_apps:
- { name: chat_web_app, env: beta, port: 8083 } - { name: chat_web_app, env: beta, port: 8083 }
- { name: chat_backend, env: prod, port: 8003 } - { name: chat_backend, env: prod, port: 8003 }
- { name: chat_backend, env: beta, port: 8013 } - { name: chat_backend, env: beta, port: 8013 }
# monica_site worker NOT here — adama host_apps sets compose_profiles: [worker]. # monica_site / college_craft / print_forge workers NOT here — adama sets compose_profiles: [worker].
- { name: monica_site, env: prod, port: 8004 } - { name: monica_site, env: prod, port: 8004 }
- { name: monica_site, env: beta, port: 8014 } - { name: monica_site, env: beta, port: 8014 }
- { name: url_shortening_service, env: prod, port: 8005 }
- { name: url_shortening_service, env: beta, port: 8015 }
- { name: college_craft, env: prod, port: 8006 }
- { name: college_craft, env: beta, port: 8016 }
- { name: print_forge, env: prod, port: 8007 }
- { name: print_forge, env: beta, port: 8019 }
- { name: abc_be, env: beta, port: 8017 }
- { name: abc_worker, env: beta, port: 8018 }
- { name: abc_fe, env: beta, port: 8085 }
- { name: livekit, env: beta, port: 7880 }
+30
View File
@@ -0,0 +1,30 @@
---
# Mirrors roslin for active/active. Ports MUST match adama/roslin so NPM
# upstreams can balance apollo:PORT with the other app hosts.
host_apps:
- { name: company_site, env: prod, port: 8000 }
- { name: dta_service, env: prod, port: 8001 }
- { 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 }
- { name: chat_web_app, env: beta, port: 8083 }
- { name: chat_backend, env: prod, port: 8003 }
- { name: chat_backend, env: beta, port: 8013 }
# monica_site / college_craft / print_forge workers NOT here — adama sets compose_profiles: [worker].
- { name: monica_site, env: prod, port: 8004 }
- { name: monica_site, env: beta, port: 8014 }
- { name: url_shortening_service, env: prod, port: 8005 }
- { name: url_shortening_service, env: beta, port: 8015 }
- { name: college_craft, env: prod, port: 8006 }
- { name: college_craft, env: beta, port: 8016 }
- { name: print_forge, env: prod, port: 8007 }
- { name: print_forge, env: beta, port: 8019 }
- { name: abc_be, env: beta, port: 8017 }
- { name: abc_worker, env: beta, port: 8018 }
- { name: abc_fe, env: beta, port: 8085 }
- { name: livekit, env: beta, port: 7880 }
+15 -3
View File
@@ -1,18 +1,30 @@
--- ---
# Mirrors adama for active/active. Ports MUST match adama so NPM upstreams # Mirrors adama for active/active. Ports MUST match adama/starbuck/apollo so
# can balance adama:PORT and roslin:PORT for the same workload. # NPM upstreams can balance roslin:PORT with the other app hosts.
host_apps: host_apps:
- { name: company_site, env: prod, port: 8000 } - { name: company_site, env: prod, port: 8000 }
- { name: dta_service, env: prod, port: 8001 } - { name: dta_service, env: prod, port: 8001 }
- { name: dta_service, env: beta, port: 8011 } - { name: dta_service, env: beta, port: 8011 }
- { name: dta_webapp, env: prod, port: 8080 } - { name: dta_webapp, env: prod, port: 8080 }
- { name: dta_webapp, env: beta, port: 8081 } - { 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 } - { name: scha, env: prod, port: 8002 }
# optional: - { name: scha, env: beta, port: 8012 } # optional: - { name: scha, env: beta, port: 8012 }
- { name: chat_web_app, env: prod, port: 8082 } - { name: chat_web_app, env: prod, port: 8082 }
- { name: chat_web_app, env: beta, port: 8083 } - { name: chat_web_app, env: beta, port: 8083 }
- { name: chat_backend, env: prod, port: 8003 } - { name: chat_backend, env: prod, port: 8003 }
- { name: chat_backend, env: beta, port: 8013 } - { name: chat_backend, env: beta, port: 8013 }
# monica_site worker NOT here — adama host_apps sets compose_profiles: [worker]. # monica_site / college_craft / print_forge workers NOT here — adama sets compose_profiles: [worker].
- { name: monica_site, env: prod, port: 8004 } - { name: monica_site, env: prod, port: 8004 }
- { name: monica_site, env: beta, port: 8014 } - { name: monica_site, env: beta, port: 8014 }
- { name: url_shortening_service, env: prod, port: 8005 }
- { name: url_shortening_service, env: beta, port: 8015 }
- { name: college_craft, env: prod, port: 8006 }
- { name: college_craft, env: beta, port: 8016 }
- { name: print_forge, env: prod, port: 8007 }
- { name: print_forge, env: beta, port: 8019 }
- { name: abc_be, env: beta, port: 8017 }
- { name: abc_worker, env: beta, port: 8018 }
- { name: abc_fe, env: beta, port: 8085 }
- { name: livekit, env: beta, port: 7880 }
+30
View File
@@ -0,0 +1,30 @@
---
# Mirrors roslin for active/active. Ports MUST match adama/roslin so NPM
# upstreams can balance starbuck:PORT with the other app hosts.
host_apps:
- { name: company_site, env: prod, port: 8000 }
- { name: dta_service, env: prod, port: 8001 }
- { 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 }
- { name: chat_web_app, env: beta, port: 8083 }
- { name: chat_backend, env: prod, port: 8003 }
- { name: chat_backend, env: beta, port: 8013 }
# monica_site / college_craft / print_forge workers NOT here — adama sets compose_profiles: [worker].
- { name: monica_site, env: prod, port: 8004 }
- { name: monica_site, env: beta, port: 8014 }
- { name: url_shortening_service, env: prod, port: 8005 }
- { name: url_shortening_service, env: beta, port: 8015 }
- { name: college_craft, env: prod, port: 8006 }
- { name: college_craft, env: beta, port: 8016 }
- { name: print_forge, env: prod, port: 8007 }
- { name: print_forge, env: beta, port: 8019 }
- { name: abc_be, env: beta, port: 8017 }
- { name: abc_worker, env: beta, port: 8018 }
- { name: abc_fe, env: beta, port: 8085 }
- { name: livekit, env: beta, port: 7880 }
+4
View File
@@ -7,5 +7,9 @@ all:
ansible_host: 10.0.0.77 ansible_host: 10.0.0.77
roslin: roslin:
ansible_host: 10.0.0.176 ansible_host: 10.0.0.176
starbuck:
ansible_host: 10.0.0.44
apollo:
ansible_host: 10.0.0.7
ai-server-4080: ai-server-4080:
ansible_host: 10.0.0.128 ansible_host: 10.0.0.128
+2 -2
View File
@@ -104,8 +104,8 @@
- _legacy_systemd_probe.stdout | default('') | length > 0 - _legacy_systemd_probe.stdout | default('') | length > 0
# Optional host_apps.compose_profiles (e.g. [worker]) activates compose profiles # Optional host_apps.compose_profiles (e.g. [worker]) activates compose profiles
# on this host only. Used for monica_site dj-queue singleton on adama — without # on this host only. Used for monica_site / college_craft / print_forge dj-queue singleton on
# it, plain up --remove-orphans can drop a manually started worker. # adama — without it, plain up --remove-orphans can drop a manually started worker.
- name: "django[{{ _project }}] start containers" - name: "django[{{ _project }}] start containers"
ansible.builtin.command: ansible.builtin.command:
cmd: "docker compose -f {{ _spec.compose_file }} --env-file .env up -d --remove-orphans" cmd: "docker compose -f {{ _spec.compose_file }} --env-file .env up -d --remove-orphans"
+22
View File
@@ -28,6 +28,28 @@
proto: tcp proto: tcp
loop: "{{ ufw_allowed_tcp_ports }}" loop: "{{ ufw_allowed_tcp_ports }}"
- name: Allow extra TCP ports
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: "{{ ufw_extra_tcp_ports | default([]) }}"
- name: Allow extra TCP ports from LAN
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
from_ip: "{{ ufw_ssh_allowed_network }}"
loop: "{{ ufw_lan_tcp_ports | default([]) }}"
- name: Allow extra UDP ports or ranges
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: udp
loop: "{{ ufw_extra_udp_ports | default([]) }}"
- name: Enable UFW - name: Enable UFW
community.general.ufw: community.general.ufw:
state: enabled state: enabled
+8
View File
@@ -30,9 +30,17 @@ server {
root {{ (app_catalog[a.name].webroot_pattern | default(web_static_root ~ '/{env}_' ~ a.name)) | replace('{env}', a.env) }}; root {{ (app_catalog[a.name].webroot_pattern | default(web_static_root ~ '/{env}_' ~ a.name)) | replace('{env}', a.env) }};
index index.html; 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 / { location / {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }
{% endif %}
location ~* \.(?:js|css|woff2?|png|jpg|jpeg|gif|svg|ico)$ { location ~* \.(?:js|css|woff2?|png|jpg|jpeg|gif|svg|ico)$ {
expires 7d; expires 7d;
+3 -3
View File
@@ -14,10 +14,10 @@ Usage: $(basename "$0") [HOST] [OPTIONS]
Deploy applications with deploy-apps.yml. Deploy applications with deploy-apps.yml.
HOST Optional. Limit to one host: adama, roslin, or ai-server-4080. HOST Optional. Limit to one host: adama, roslin, starbuck, apollo, or ai-server-4080.
Options: Options:
--app NAME App to deploy (company_site, dta_service, dta_webapp, scha, chat_web_app, chat_backend, monica_site) --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, livekit)
--env ENV Environment: beta or prod --env ENV Environment: beta or prod
--ref REF Git ref/sha to deploy (default: master) --ref REF Git ref/sha to deploy (default: master)
--check Dry run --check Dry run
@@ -67,7 +67,7 @@ while [[ $# -gt 0 ]]; do
EXTRA_VARS+=(-e "$2") EXTRA_VARS+=(-e "$2")
shift 2 shift 2
;; ;;
adama|roslin|ai-server-4080) adama|roslin|starbuck|apollo|ai-server-4080)
LIMIT="$1" LIMIT="$1"
shift shift
;; ;;
+2 -2
View File
@@ -18,7 +18,7 @@ On ai-server-4080 also: observability (Loki + Prometheus + Grafana) when
observability_stack is true, and SearxNG when searxng_stack is true observability_stack is true, and SearxNG when searxng_stack is true
(chat_backend grounded search on :8088). (chat_backend grounded search on :8088).
HOST Optional. Limit to one host: adama, roslin, or ai-server-4080. HOST Optional. Limit to one host: adama, roslin, starbuck, apollo, or ai-server-4080.
Omit to run against all webservers. Omit to run against all webservers.
Options: Options:
@@ -59,7 +59,7 @@ while [[ $# -gt 0 ]]; do
EXTRA_ARGS+=(--ask-become-pass) EXTRA_ARGS+=(--ask-become-pass)
shift shift
;; ;;
adama|roslin|ai-server-4080) adama|roslin|starbuck|apollo|ai-server-4080)
LIMIT="$1" LIMIT="$1"
shift shift
;; ;;