diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index a77cfec..fd7ece5 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -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 ` 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 /` 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/.abc.aimloperations/html`; no prod `host_apps` row | @@ -234,6 +249,8 @@ a DB. - `host_apps` (`host_vars/.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 `_`, 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)): diff --git a/README.md b/README.md index 343ab66..4fea127 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/inventory/group_vars/all.yml b/inventory/group_vars/all.yml index a89fd7e..9671b25 100644 --- a/inventory/group_vars/all.yml +++ b/inventory/group_vars/all.yml @@ -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 diff --git a/inventory/host_vars/adama.yml b/inventory/host_vars/adama.yml index bc0d0ea..3e7877c 100644 --- a/inventory/host_vars/adama.yml +++ b/inventory/host_vars/adama.yml @@ -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 } diff --git a/inventory/host_vars/ai-server-4080.yml b/inventory/host_vars/ai-server-4080.yml index 2272dfa..451aaa6 100644 --- a/inventory/host_vars/ai-server-4080.yml +++ b/inventory/host_vars/ai-server-4080.yml @@ -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 } diff --git a/inventory/host_vars/apollo.yml b/inventory/host_vars/apollo.yml index f4e8442..a8375fd 100644 --- a/inventory/host_vars/apollo.yml +++ b/inventory/host_vars/apollo.yml @@ -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 } diff --git a/inventory/host_vars/roslin.yml b/inventory/host_vars/roslin.yml index 9b5a3ed..99e3db3 100644 --- a/inventory/host_vars/roslin.yml +++ b/inventory/host_vars/roslin.yml @@ -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 } diff --git a/inventory/host_vars/starbuck.yml b/inventory/host_vars/starbuck.yml index c100736..8e1db29 100644 --- a/inventory/host_vars/starbuck.yml +++ b/inventory/host_vars/starbuck.yml @@ -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 } diff --git a/playbooks/deploy-apps.yml b/playbooks/deploy-apps.yml index 9c81255..47d4cd6 100644 --- a/playbooks/deploy-apps.yml +++ b/playbooks/deploy-apps.yml @@ -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= diff --git a/playbooks/stop-apps.yml b/playbooks/stop-apps.yml new file mode 100644 index 0000000..8dff7ea --- /dev/null +++ b/playbooks/stop-apps.yml @@ -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 diff --git a/roles/app-deploy/tasks/main.yml b/roles/app-deploy/tasks/main.yml index 761547e..3fc4b21 100644 --- a/roles/app-deploy/tasks/main.yml +++ b/roles/app-deploy/tasks/main.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: diff --git a/roles/app-deploy/tasks/stop.yml b/roles/app-deploy/tasks/stop.yml new file mode 100644 index 0000000..65d89f5 --- /dev/null +++ b/roles/app-deploy/tasks/stop.yml @@ -0,0 +1,47 @@ +--- +# Stop one app+env. CI/manual must pass: app= app_env= +# +# 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= -e app_env=" + 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 }}" diff --git a/roles/app-deploy/tasks/stop_django.yml b/roles/app-deploy/tasks/stop_django.yml new file mode 100644 index 0000000..73921ea --- /dev/null +++ b/roles/app-deploy/tasks/stop_django.yml @@ -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 diff --git a/roles/web-static/tasks/main.yml b/roles/web-static/tasks/main.yml index 3e42432..d5ece40 100644 --- a/roles/web-static/tasks/main.yml +++ b/roles/web-static/tasks/main.yml @@ -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 diff --git a/scripts/stop.sh b/scripts/stop.sh new file mode 100755 index 0000000..b144b06 --- /dev/null +++ b/scripts/stop.sh @@ -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 <&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[@]}"