Add stop.sh (compose down) and disable print_forge (#34)
Sync runner checkout / sync (push) Successful in 6s
Sync runner checkout / sync (push) Successful in 6s
## Summary - Closes [#33](#33). - Add `scripts/stop.sh` → `playbooks/stop-apps.yml` → `docker compose down` for one `--app` + `--env` (no `-v`; checkouts/secrets stay). - `host_apps.enabled: false` skips deploy (including CI `--app`) so a stopped app cannot come back. Rows stay for ports and `stop.sh`. - Disable `print_forge` beta + prod on all app hosts. After merge (or from this branch): `./scripts/stop.sh --app print_forge --env prod` and `--env beta`. ## Test plan - [ ] `./scripts/stop.sh --help` shows required `--app` / `--env` - [ ] `ansible-playbook playbooks/stop-apps.yml --syntax-check -e app=print_forge -e app_env=prod` - [ ] `./scripts/stop.sh --app print_forge --env prod` compose-downs `print_forge_prod` on all webservers - [ ] `./scripts/stop.sh --app print_forge --env beta` compose-downs `print_forge_beta` (including adama worker) - [ ] `./scripts/deploy.sh --app print_forge --env prod --check` skips disabled rows (does not start containers) - [ ] NPM: `print-forge-preview.aimloperations.com` will 502 until the proxy host is disabled (out of scope) Reviewed-on: #34
This commit was merged in pull request #34.
This commit is contained in:
+38
-8
@@ -30,6 +30,7 @@ flowchart TB
|
||||
|----------|------|----------|---------------|
|
||||
| **Provision** | New VM, OS change, firewall, Docker install | `site.yml` | ai-server-4080 — run manually |
|
||||
| **Deploy** | Green unit tests on `master` | `deploy-apps.yml` | Gitea Act runner on ai-server-4080 |
|
||||
| **Stop** | Take a compose app down | `stop-apps.yml` (`scripts/stop.sh`) | ai-server-4080 — run manually |
|
||||
|
||||
Both pipelines share the same inventory (`inventory/hosts.yml`).
|
||||
|
||||
@@ -65,7 +66,8 @@ server-infra/
|
||||
│ └── ai-server-4080.yml # control node / act runner / SearxNG / observability
|
||||
├── playbooks/
|
||||
│ ├── site.yml # Phase 1: provision
|
||||
│ └── deploy-apps.yml # Phase 2: CI deploy
|
||||
│ ├── deploy-apps.yml # Phase 2: CI deploy
|
||||
│ └── stop-apps.yml # compose down one app+env
|
||||
├── roles/
|
||||
│ ├── common/ # Base packages
|
||||
│ ├── ufw/ # Firewall
|
||||
@@ -80,7 +82,8 @@ server-infra/
|
||||
│ └── web-static/ # nginx container serving /var/www builds
|
||||
└── scripts/
|
||||
├── provision.sh # Wrapper with --limit support
|
||||
└── deploy.sh # Wrapper for deploy playbook
|
||||
├── deploy.sh # Wrapper for deploy playbook
|
||||
└── stop.sh # Wrapper for stop playbook (compose down)
|
||||
```
|
||||
|
||||
## Prerequisites (One-Time Bootstrap)
|
||||
@@ -174,6 +177,18 @@ New hosts need the one-time passwordless sudo bootstrap in
|
||||
./scripts/deploy.sh apollo
|
||||
```
|
||||
|
||||
### Stop a compose app
|
||||
|
||||
```bash
|
||||
./scripts/stop.sh --app print_forge --env prod
|
||||
./scripts/stop.sh --app print_forge --env beta
|
||||
./scripts/stop.sh adama --app chat_backend --env beta --check
|
||||
```
|
||||
|
||||
`stop.sh` is `docker compose down` for one `--app` + `--env` (no `-v`). Checkouts and
|
||||
secrets stay. Next `deploy.sh` of that app+env starts it again unless `host_apps`
|
||||
sets `enabled: false`. Node-static apps have no compose project; stop.sh skips them.
|
||||
|
||||
Under the hood, scripts pass `--limit <hostname>` to `ansible-playbook`.
|
||||
|
||||
## Phase 1: Provision (`site.yml`)
|
||||
@@ -218,7 +233,7 @@ After Docker install, re-SSH so the `docker` group membership takes effect.
|
||||
| `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 |
|
||||
| `print_forge` | django (docker) | all webservers | beta + prod | **stopped** (`enabled: false`, [#33](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/33)). Ports reserved. Re-enable + `deploy.sh` to bring back. Worker was adama-only. |
|
||||
| `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 |
|
||||
@@ -234,6 +249,8 @@ a DB.
|
||||
- `host_apps` (`host_vars/<host>.yml`) — which app+env+port runs on that host.
|
||||
Optional `compose_profiles: [worker]` activates docker compose profiles on that
|
||||
host only (used for `monica_site` / `college_craft` / `print_forge` dj-queue singleton on adama).
|
||||
Optional `enabled: false` skips deploy (CI included) but keeps the row for ports
|
||||
and `stop.sh`. Omit the key (or `true`) to deploy as before.
|
||||
- Django app = one compose project per env: project name `<app>_<env>`, host port from `host_apps`.
|
||||
Ports match across app hosts so NPM can balance `adama:PORT` + `roslin:PORT` + `starbuck:PORT` + `apollo:PORT`.
|
||||
|
||||
@@ -252,7 +269,7 @@ future beta replica.
|
||||
| monica_site | 8014 | 8004 | all webservers |
|
||||
| url_shortening_service | 8015 | 8005 | all webservers |
|
||||
| college_craft | 8016 | 8006 | all webservers |
|
||||
| print_forge | **8019** | **8007** | all webservers |
|
||||
| print_forge | **8019** (*stopped*) | **8007** (*stopped*) | all webservers (`enabled: false`) |
|
||||
| abc_be | **8017** | 8009 (*not deployed*) | all webservers |
|
||||
| abc_worker | **8018** | 8008 (*not deployed*) | all webservers |
|
||||
| dta_webapp (nginx) | 8081 | 8080 | all webservers |
|
||||
@@ -285,7 +302,16 @@ chat_backend secrets must use `SEARXNG_BASE_URL=http://10.0.0.128:8088`.
|
||||
--app company_site --env prod --ref "${{ gitea.sha }}"
|
||||
```
|
||||
3. `deploy-apps.yml` runs against `webservers`; each host deploys only the
|
||||
matching app+env from its `host_apps`.
|
||||
matching **enabled** app+env from its `host_apps` (`enabled: false` is skipped).
|
||||
|
||||
To stop a compose app without deleting inventory:
|
||||
|
||||
```bash
|
||||
./scripts/stop.sh --app print_forge --env prod
|
||||
```
|
||||
|
||||
Set `enabled: false` on that `host_apps` row so the next CI deploy cannot start it
|
||||
again. `stop.sh` still matches disabled rows.
|
||||
|
||||
### `app-deploy` role behavior
|
||||
|
||||
@@ -329,7 +355,7 @@ point each domain at the backend(s):
|
||||
| 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 |
|
||||
| print_forge | `printforgeprints.com` (+ www); `print-forge-preview.aimloperations.com` (beta) — **app stopped** (`enabled: false`). Disable NPM hosts or expect 502. | `:8007` / `:8019` reserved |
|
||||
| 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 |
|
||||
@@ -406,7 +432,10 @@ 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):
|
||||
`print_forge` extra env (control-node secrets, not in git) — **currently stopped**
|
||||
([#33](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/33)):
|
||||
`host_apps` rows stay with `enabled: false`. Bring back with `enabled` removed/true
|
||||
then `deploy.sh`. Secrets kept:
|
||||
`~/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` /
|
||||
@@ -421,7 +450,8 @@ 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`).
|
||||
App Gitea workflow still calls `--app print_forge` on `master`; deploy **no-ops**
|
||||
while `enabled: false`.
|
||||
|
||||
`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)):
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ ansible adama -m ping
|
||||
|
||||
# Provision all hosts
|
||||
./scripts/provision.sh
|
||||
|
||||
# Stop a compose app (does not remove volumes/checkouts)
|
||||
./scripts/stop.sh --app print_forge --env prod
|
||||
./scripts/stop.sh --app print_forge --env beta
|
||||
```
|
||||
|
||||
See [IMPLEMENTATION.md](IMPLEMENTATION.md) for full architecture, CI/CD plan, and phase breakdown.
|
||||
|
||||
@@ -59,6 +59,7 @@ secrets_dir: "{{ lookup('ansible.builtin.env', 'HOME') }}/Documents/secrets"
|
||||
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
|
||||
|
||||
@@ -26,8 +26,9 @@ host_apps:
|
||||
- { 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] }
|
||||
# print_forge stopped (#33) — enabled: false keeps ports; stop.sh still matches.
|
||||
- { name: print_forge, env: prod, port: 8007, compose_profiles: [worker], enabled: false }
|
||||
- { name: print_forge, env: beta, port: 8019, compose_profiles: [worker], enabled: false }
|
||||
- { name: abc_be, env: beta, port: 8017 }
|
||||
- { name: abc_worker, env: beta, port: 8018 }
|
||||
- { name: abc_fe, env: beta, port: 8085 }
|
||||
|
||||
@@ -43,8 +43,8 @@ host_apps:
|
||||
- { 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: print_forge, env: prod, port: 8007, enabled: false }
|
||||
- { name: print_forge, env: beta, port: 8019, enabled: false }
|
||||
- { name: abc_be, env: beta, port: 8017 }
|
||||
- { name: abc_worker, env: beta, port: 8018 }
|
||||
- { name: abc_fe, env: beta, port: 8085 }
|
||||
|
||||
@@ -22,8 +22,8 @@ host_apps:
|
||||
- { 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: print_forge, env: prod, port: 8007, enabled: false }
|
||||
- { name: print_forge, env: beta, port: 8019, enabled: false }
|
||||
- { name: abc_be, env: beta, port: 8017 }
|
||||
- { name: abc_worker, env: beta, port: 8018 }
|
||||
- { name: abc_fe, env: beta, port: 8085 }
|
||||
|
||||
@@ -22,8 +22,8 @@ host_apps:
|
||||
- { 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: print_forge, env: prod, port: 8007, enabled: false }
|
||||
- { name: print_forge, env: beta, port: 8019, enabled: false }
|
||||
- { name: abc_be, env: beta, port: 8017 }
|
||||
- { name: abc_worker, env: beta, port: 8018 }
|
||||
- { name: abc_fe, env: beta, port: 8085 }
|
||||
|
||||
@@ -22,8 +22,8 @@ host_apps:
|
||||
- { 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: print_forge, env: prod, port: 8007, enabled: false }
|
||||
- { name: print_forge, env: beta, port: 8019, enabled: false }
|
||||
- { name: abc_be, env: beta, port: 8017 }
|
||||
- { name: abc_worker, env: beta, port: 8018 }
|
||||
- { name: abc_fe, env: beta, port: 8085 }
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
# Phase 2: CI-triggered app deployment.
|
||||
#
|
||||
# Runs against all webservers; each host deploys only the apps listed in its
|
||||
# host_apps. Deploy exactly one app+env at a pinned ref via extra-vars:
|
||||
# Runs against all webservers; each host deploys only enabled apps listed in
|
||||
# its host_apps (enabled: false is skipped). Deploy exactly one app+env at a
|
||||
# pinned ref via extra-vars:
|
||||
#
|
||||
# ansible-playbook playbooks/deploy-apps.yml \
|
||||
# -e app=company_site -e app_env=prod -e app_ref=<sha>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
# Stop one docker-compose app+env. Inverse of deploy-apps.yml start.
|
||||
#
|
||||
# ansible-playbook playbooks/stop-apps.yml -e app=print_forge -e app_env=prod
|
||||
#
|
||||
# host_apps rows with enabled: false are still valid stop targets (so a
|
||||
# disabled app can be taken down). Node-static apps are skipped.
|
||||
- name: Stop applications
|
||||
hosts: webservers
|
||||
become: true
|
||||
tasks:
|
||||
- name: Stop requested compose apps
|
||||
ansible.builtin.include_role:
|
||||
name: app-deploy
|
||||
tasks_from: stop.yml
|
||||
@@ -6,24 +6,21 @@
|
||||
# at branch master.
|
||||
#
|
||||
# host_apps (host_vars) lists what runs on THIS host; app_catalog (group_vars)
|
||||
# describes how each app is built.
|
||||
# describes how each app is built. host_apps.enabled: false (omit = true)
|
||||
# keeps the row for ports/stop.sh but skips deploy so CI cannot resurrect it.
|
||||
|
||||
- name: Classify catalog by type
|
||||
ansible.builtin.set_fact:
|
||||
django_names: "{{ app_catalog | dict2items | selectattr('value.type', 'equalto', 'django') | map(attribute='key') | list }}"
|
||||
node_names: "{{ app_catalog | dict2items | selectattr('value.type', 'equalto', 'node-static') | map(attribute='key') | list }}"
|
||||
|
||||
- name: Resolve deploy targets for {{ inventory_hostname }}
|
||||
- name: Resolve matching host_apps for {{ inventory_hostname }}
|
||||
ansible.builtin.set_fact:
|
||||
deploy_targets: >-
|
||||
matching_host_apps: >-
|
||||
{{ (host_apps | default([]) | selectattr('name', 'equalto', app | default('')) | selectattr('env', 'equalto', app_env | default('')) | list)
|
||||
if (app is defined and app_env is defined)
|
||||
else (host_apps | default([])) }}
|
||||
|
||||
- name: Show deploy targets
|
||||
ansible.builtin.debug:
|
||||
msg: "ref={{ app_ref | default('(per-app default branch)') }} targets={{ deploy_targets | map(attribute='name') | zip(deploy_targets | map(attribute='env')) | list }}"
|
||||
|
||||
# Explicit --app/--env with no host_apps match used to silently no-op Django
|
||||
# and still refresh web-static (looked like a "static-only" deploy). Fail loud.
|
||||
- name: Fail when requested app+env is not on this host
|
||||
@@ -36,7 +33,22 @@
|
||||
when:
|
||||
- app is defined
|
||||
- app_env is defined
|
||||
- deploy_targets | length == 0
|
||||
- matching_host_apps | length == 0
|
||||
|
||||
- name: "Drop disabled host_apps (enabled: false)"
|
||||
ansible.builtin.set_fact:
|
||||
deploy_targets: "{{ matching_host_apps | rejectattr('enabled', 'equalto', false) | list }}"
|
||||
|
||||
- name: Show skipped disabled apps
|
||||
ansible.builtin.debug:
|
||||
msg: "skip disabled {{ item.name }}/{{ item.env }} on {{ inventory_hostname }}"
|
||||
loop: "{{ matching_host_apps | selectattr('enabled', 'equalto', false) | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}/{{ item.env }}"
|
||||
|
||||
- name: Show deploy targets
|
||||
ansible.builtin.debug:
|
||||
msg: "ref={{ app_ref | default('(per-app default branch)') }} targets={{ deploy_targets | map(attribute='name') | zip(deploy_targets | map(attribute='env')) | list }}"
|
||||
|
||||
- name: Fail when requested app missing from app_catalog
|
||||
ansible.builtin.fail:
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
# Stop one app+env. CI/manual must pass: app=<name> app_env=<beta|prod>
|
||||
#
|
||||
# Matches host_apps including enabled: false. Hosts without that row skip.
|
||||
# Django/compose → docker compose down (no -v). Node-static → message only.
|
||||
|
||||
- name: Fail unless app and app_env extra-vars set
|
||||
ansible.builtin.fail:
|
||||
msg: "stop-apps.yml requires -e app=<name> -e app_env=<beta|prod>"
|
||||
when: app is not defined or app_env is not defined
|
||||
|
||||
- name: Classify catalog by type
|
||||
ansible.builtin.set_fact:
|
||||
django_names: "{{ app_catalog | dict2items | selectattr('value.type', 'equalto', 'django') | map(attribute='key') | list }}"
|
||||
node_names: "{{ app_catalog | dict2items | selectattr('value.type', 'equalto', 'node-static') | map(attribute='key') | list }}"
|
||||
|
||||
- name: Fail when requested app missing from app_catalog
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
app={{ app }} is not in app_catalog. Known: {{ app_catalog.keys() | list }}.
|
||||
when: app not in app_catalog
|
||||
|
||||
- name: Resolve stop targets for {{ inventory_hostname }}
|
||||
ansible.builtin.set_fact:
|
||||
stop_targets: "{{ host_apps | default([]) | selectattr('name', 'equalto', app) | selectattr('env', 'equalto', app_env) | list }}"
|
||||
|
||||
- name: Skip {{ inventory_hostname }} — no host_apps row for {{ app }}/{{ app_env }}
|
||||
ansible.builtin.debug:
|
||||
msg: "skip {{ inventory_hostname }}: no host_apps entry for app={{ app }} env={{ app_env }}"
|
||||
when: stop_targets | length == 0
|
||||
|
||||
- name: Skip node-static {{ app }}/{{ app_env }} (no compose project)
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ app }} is node-static. stop.sh only compose-downs Django apps.
|
||||
Drop the host_apps row (or set enabled: false) and re-run deploy.sh
|
||||
so web-static regenerates nginx without that vhost.
|
||||
when:
|
||||
- stop_targets | length > 0
|
||||
- app in node_names
|
||||
|
||||
- name: Stop Django (docker compose) apps
|
||||
ansible.builtin.include_tasks: stop_django.yml
|
||||
loop: "{{ stop_targets | selectattr('name', 'in', django_names) | list }}"
|
||||
loop_control:
|
||||
loop_var: app_item
|
||||
label: "{{ app_item.name }}/{{ app_item.env }}"
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
# Compose-down one Django app+env. Called per item with loop_var app_item.
|
||||
# No `down -v` — volumes stay. Checkout and secrets stay.
|
||||
|
||||
- name: "stop[{{ app_item.name }}/{{ app_item.env }}] locals"
|
||||
ansible.builtin.set_fact:
|
||||
_spec: "{{ app_catalog[app_item.name] }}"
|
||||
_src: "{{ apps_src_dir }}/{{ app_item.name }}_{{ app_item.env }}"
|
||||
_project: "{{ app_item.name }}_{{ app_item.env }}"
|
||||
|
||||
- name: "stop[{{ _project }}] checkout dir"
|
||||
ansible.builtin.stat:
|
||||
path: "{{ _src }}"
|
||||
register: _src_stat
|
||||
|
||||
- name: "stop[{{ _project }}] compose file"
|
||||
ansible.builtin.stat:
|
||||
path: "{{ _src }}/{{ _spec.compose_file }}"
|
||||
register: _compose_stat
|
||||
when: _src_stat.stat.exists
|
||||
|
||||
- name: "stop[{{ _project }}] env file"
|
||||
ansible.builtin.stat:
|
||||
path: "{{ _src }}/.env"
|
||||
register: _env_stat
|
||||
when: _src_stat.stat.exists
|
||||
|
||||
- name: "stop[{{ _project }}] use compose down"
|
||||
ansible.builtin.set_fact:
|
||||
_use_compose: "{{ _src_stat.stat.exists and _compose_stat is defined and _compose_stat.stat.exists }}"
|
||||
|
||||
- name: "stop[{{ _project }}] compose down"
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
docker compose -f {{ _spec.compose_file }}
|
||||
{{ '--env-file .env' if (_env_stat is defined and _env_stat.stat.exists) else '' }}
|
||||
down
|
||||
chdir: "{{ _src }}"
|
||||
environment:
|
||||
COMPOSE_PROJECT_NAME: "{{ _project }}"
|
||||
WEB_PORT: "{{ app_item.port }}"
|
||||
COMPOSE_PROFILES: "{{ (app_item.compose_profiles | default([])) | join(',') }}"
|
||||
become: true
|
||||
become_user: "{{ admin_user }}"
|
||||
when: _use_compose | bool
|
||||
changed_when: true
|
||||
|
||||
- name: "stop[{{ _project }}] find leftover project containers"
|
||||
ansible.builtin.command:
|
||||
cmd: docker ps -aq --filter label=com.docker.compose.project={{ _project }}
|
||||
register: _leftovers
|
||||
changed_when: false
|
||||
become: true
|
||||
when: not (_use_compose | bool)
|
||||
|
||||
- name: "stop[{{ _project }}] remove leftover project containers"
|
||||
ansible.builtin.command:
|
||||
cmd: "docker rm -f {{ _leftovers.stdout_lines | join(' ') }}"
|
||||
become: true
|
||||
when:
|
||||
- not (_use_compose | bool)
|
||||
- _leftovers is defined
|
||||
- _leftovers.stdout_lines | length > 0
|
||||
changed_when: true
|
||||
|
||||
- name: "stop[{{ _project }}] nothing to stop (no checkout)"
|
||||
ansible.builtin.debug:
|
||||
msg: "no checkout at {{ _src }} and no containers for project {{ _project }}"
|
||||
when:
|
||||
- not (_use_compose | bool)
|
||||
- _leftovers is defined
|
||||
- _leftovers.stdout_lines | length == 0
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
- name: web-static | this host's static apps
|
||||
ansible.builtin.set_fact:
|
||||
_static_apps: "{{ host_apps | default([]) | selectattr('name', 'in', _node_names) | list }}"
|
||||
_static_apps: "{{ host_apps | default([]) | rejectattr('enabled', 'equalto', false) | selectattr('name', 'in', _node_names) | list }}"
|
||||
|
||||
- name: web-static | configure and run
|
||||
when: _static_apps | length > 0
|
||||
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
LIMIT=""
|
||||
EXTRA_ARGS=()
|
||||
EXTRA_VARS=()
|
||||
APP=""
|
||||
APP_ENV=""
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [HOST] --app NAME --env ENV [OPTIONS]
|
||||
|
||||
Stop a docker-compose app (compose down). Inverse of deploy.sh.
|
||||
|
||||
Does not remove volumes, checkouts, or secrets. Next deploy.sh of the same
|
||||
app+env will start it again unless host_apps sets enabled: false.
|
||||
|
||||
Node-static apps have no compose project — stop.sh skips them.
|
||||
|
||||
HOST Optional. Limit to one host: adama, roslin, starbuck, apollo, or ai-server-4080.
|
||||
|
||||
Options:
|
||||
--app NAME Required. App to stop (same names as deploy.sh)
|
||||
--env ENV Required. Environment: beta or prod
|
||||
--check Dry run
|
||||
--diff Show diffs
|
||||
--extra-vars V Pass raw extra vars
|
||||
-h, --help Show this help
|
||||
|
||||
Examples:
|
||||
$(basename "$0") --app print_forge --env prod
|
||||
$(basename "$0") --app print_forge --env beta
|
||||
$(basename "$0") adama --app chat_backend --env beta --check
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--app)
|
||||
APP="$2"
|
||||
EXTRA_VARS+=(-e "app=$2")
|
||||
shift 2
|
||||
;;
|
||||
--env)
|
||||
APP_ENV="$2"
|
||||
EXTRA_VARS+=(-e "app_env=$2")
|
||||
shift 2
|
||||
;;
|
||||
--check)
|
||||
EXTRA_ARGS+=(--check)
|
||||
shift
|
||||
;;
|
||||
--diff)
|
||||
EXTRA_ARGS+=(--diff)
|
||||
shift
|
||||
;;
|
||||
--extra-vars)
|
||||
EXTRA_VARS+=(-e "$2")
|
||||
shift 2
|
||||
;;
|
||||
adama|roslin|starbuck|apollo|ai-server-4080)
|
||||
LIMIT="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$APP" || -z "$APP_ENV" ]]; then
|
||||
echo "error: --app and --env are required" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CMD=(ansible-playbook playbooks/stop-apps.yml "${EXTRA_ARGS[@]}" "${EXTRA_VARS[@]}")
|
||||
if [[ -n "$LIMIT" ]]; then
|
||||
CMD+=(--limit "$LIMIT")
|
||||
echo "==> Stopping ${APP}/${APP_ENV} on: $LIMIT"
|
||||
else
|
||||
echo "==> Stopping ${APP}/${APP_ENV} on all webservers"
|
||||
fi
|
||||
|
||||
exec "${CMD[@]}"
|
||||
Reference in New Issue
Block a user