updates
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
---
|
||||
# Deploy one Django app+env as its own docker compose project.
|
||||
# Called per item with loop_var app_item = { name, env, port }.
|
||||
|
||||
- name: "django[{{ 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 }}"
|
||||
_envfile: "{{ apps_env_dir }}/{{ app_item.name }}_{{ app_item.env }}.env"
|
||||
_secret_src: "{{ secrets_dir }}/{{ app_item.name }}/{{ app_item.name }}_{{ app_item.env }}.env"
|
||||
_project: "{{ app_item.name }}_{{ app_item.env }}"
|
||||
_ref: "{{ app_ref | default(app_catalog[app_item.name].default_branch) }}"
|
||||
|
||||
- name: "django[{{ _project }}] ensure base dirs"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ admin_user }}"
|
||||
group: "{{ admin_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ apps_src_dir }}"
|
||||
- "{{ apps_env_dir }}"
|
||||
|
||||
- name: "django[{{ _project }}] check local secret exists"
|
||||
ansible.builtin.stat:
|
||||
path: "{{ _secret_src }}"
|
||||
register: _secret_stat
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: "django[{{ _project }}] fail when local secret missing"
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Missing local secret {{ _secret_src }} on the control node.
|
||||
Create it (DATABASE_URL to the shared external Postgres, DJANGO_SECRET_KEY,
|
||||
WEB_PORT={{ app_item.port }}, etc.) before deploying. It is never committed
|
||||
to git.
|
||||
when: not _secret_stat.stat.exists
|
||||
|
||||
- name: "django[{{ _project }}] push secret to {{ _envfile }}"
|
||||
ansible.builtin.copy:
|
||||
src: "{{ _secret_src }}"
|
||||
dest: "{{ _envfile }}"
|
||||
owner: "{{ admin_user }}"
|
||||
group: "{{ admin_user }}"
|
||||
mode: "0600"
|
||||
|
||||
- name: "django[{{ _project }}] checkout {{ _ref }}"
|
||||
ansible.builtin.git:
|
||||
repo: "{{ _spec.repo }}"
|
||||
dest: "{{ _src }}"
|
||||
version: "{{ _ref }}"
|
||||
force: true
|
||||
accept_hostkey: true
|
||||
become: true
|
||||
become_user: "{{ admin_user }}"
|
||||
|
||||
- name: "django[{{ _project }}] install .env into checkout"
|
||||
ansible.builtin.copy:
|
||||
src: "{{ _envfile }}"
|
||||
dest: "{{ _src }}/.env"
|
||||
remote_src: true
|
||||
owner: "{{ admin_user }}"
|
||||
group: "{{ admin_user }}"
|
||||
mode: "0600"
|
||||
|
||||
- name: "django[{{ _project }}] build images"
|
||||
ansible.builtin.command:
|
||||
cmd: "docker compose -f {{ _spec.compose_file }} --env-file .env build"
|
||||
chdir: "{{ _src }}"
|
||||
environment:
|
||||
COMPOSE_PROJECT_NAME: "{{ _project }}"
|
||||
WEB_PORT: "{{ app_item.port }}"
|
||||
become: true
|
||||
become_user: "{{ admin_user }}"
|
||||
changed_when: true
|
||||
|
||||
- name: "django[{{ _project }}] start containers"
|
||||
ansible.builtin.command:
|
||||
cmd: "docker compose -f {{ _spec.compose_file }} --env-file .env up -d --remove-orphans"
|
||||
chdir: "{{ _src }}"
|
||||
environment:
|
||||
COMPOSE_PROJECT_NAME: "{{ _project }}"
|
||||
WEB_PORT: "{{ app_item.port }}"
|
||||
become: true
|
||||
become_user: "{{ admin_user }}"
|
||||
changed_when: true
|
||||
@@ -1,9 +1,56 @@
|
||||
---
|
||||
# Stub — implement after company_site is dockerized.
|
||||
# Interim: can rsync/systemd like company_site/scripts/deploy.sh
|
||||
# Generic app deployment.
|
||||
#
|
||||
# CI passes: app=<name> app_env=<beta|prod> app_ref=<git sha>
|
||||
# Manual run with none of those redeploys every app in this host's host_apps
|
||||
# at branch master.
|
||||
#
|
||||
# host_apps (host_vars) lists what runs on THIS host; app_catalog (group_vars)
|
||||
# describes how each app is built.
|
||||
|
||||
- name: App deploy not yet implemented
|
||||
- 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 }}
|
||||
ansible.builtin.set_fact:
|
||||
deploy_targets: >-
|
||||
{{ (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: >-
|
||||
app-deploy role is a stub. Set app_ref={{ app_ref | default('unset') }}.
|
||||
Implement git pull / docker compose after dockerize ticket.
|
||||
msg: "ref={{ app_ref | default('(per-app default branch)') }} targets={{ deploy_targets | map(attribute='name') | zip(deploy_targets | map(attribute='env')) | list }}"
|
||||
|
||||
- name: Deploy Django (docker compose) apps
|
||||
ansible.builtin.include_tasks: django.yml
|
||||
loop: "{{ deploy_targets | selectattr('name', 'in', django_names) | list }}"
|
||||
loop_control:
|
||||
loop_var: app_item
|
||||
label: "{{ app_item.name }}/{{ app_item.env }}"
|
||||
|
||||
- name: Run Django migrations once (shared external DB)
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
docker compose -f {{ app_catalog[item.name].compose_file }} --env-file .env
|
||||
exec -T {{ app_catalog[item.name].web_service }}
|
||||
{{ app_catalog[item.name].migrate_cmd }}
|
||||
chdir: "{{ apps_src_dir }}/{{ item.name }}_{{ item.env }}"
|
||||
environment:
|
||||
COMPOSE_PROJECT_NAME: "{{ item.name }}_{{ item.env }}"
|
||||
loop: "{{ deploy_targets | selectattr('name', 'in', django_names) | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}/{{ item.env }}"
|
||||
become: true
|
||||
become_user: "{{ admin_user }}"
|
||||
run_once: true
|
||||
changed_when: true
|
||||
|
||||
- name: Deploy node static apps
|
||||
ansible.builtin.include_tasks: node_static.yml
|
||||
loop: "{{ deploy_targets | selectattr('name', 'in', node_names) | list }}"
|
||||
loop_control:
|
||||
loop_var: app_item
|
||||
label: "{{ app_item.name }}/{{ app_item.env }}"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
# Build one node/vite static site into {{ web_static_root }}/<env>_<app>.
|
||||
# Called per item with loop_var app_item = { name, env, port }.
|
||||
# The app's own `npm run build:<env>` script is expected to output to
|
||||
# {{ web_static_root }}/<env>_dta_webapp.
|
||||
|
||||
- name: "static[{{ 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 }}"
|
||||
_webroot: "{{ (app_catalog[app_item.name].webroot_pattern | default(web_static_root ~ '/{env}_' ~ app_item.name)) | replace('{env}', app_item.env) }}"
|
||||
_ref: "{{ app_ref | default(app_catalog[app_item.name].default_branch) }}"
|
||||
|
||||
- name: "static[{{ app_item.name }}/{{ app_item.env }}] workdir"
|
||||
ansible.builtin.set_fact:
|
||||
_workdir: "{{ (_src ~ '/' ~ _spec.subdir) if (_spec.subdir | default('')) else _src }}"
|
||||
|
||||
- name: "static[{{ app_item.name }}/{{ app_item.env }}] ensure dirs"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ admin_user }}"
|
||||
group: "{{ admin_user }}"
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ apps_src_dir }}"
|
||||
- "{{ _webroot }}"
|
||||
|
||||
- name: "static[{{ app_item.name }}/{{ app_item.env }}] checkout {{ _ref }}"
|
||||
ansible.builtin.git:
|
||||
repo: "{{ _spec.repo }}"
|
||||
dest: "{{ _src }}"
|
||||
version: "{{ _ref }}"
|
||||
force: true
|
||||
accept_hostkey: true
|
||||
become: true
|
||||
become_user: "{{ admin_user }}"
|
||||
|
||||
- name: "static[{{ app_item.name }}/{{ app_item.env }}] npm ci"
|
||||
ansible.builtin.command:
|
||||
cmd: npm ci
|
||||
chdir: "{{ _workdir }}"
|
||||
become: true
|
||||
become_user: "{{ admin_user }}"
|
||||
changed_when: true
|
||||
|
||||
- name: "static[{{ app_item.name }}/{{ app_item.env }}] build ({{ app_item.env }})"
|
||||
ansible.builtin.command:
|
||||
cmd: "npm run build:{{ app_item.env }}"
|
||||
chdir: "{{ _workdir }}"
|
||||
become: true
|
||||
become_user: "{{ admin_user }}"
|
||||
changed_when: true
|
||||
@@ -1,4 +1,11 @@
|
||||
---
|
||||
- name: Allow admin user passwordless sudo for Ansible
|
||||
ansible.builtin.copy:
|
||||
dest: "/etc/sudoers.d/{{ admin_user }}"
|
||||
content: "{{ admin_user }} ALL=(ALL) NOPASSWD:ALL\n"
|
||||
mode: "0440"
|
||||
validate: visudo -cf %s
|
||||
|
||||
- name: Update apt cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
---
|
||||
- name: Create keyrings directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Add Docker GPG key
|
||||
ansible.builtin.get_url:
|
||||
url: https://download.docker.com/linux/ubuntu/gpg
|
||||
dest: /etc/apt/keyrings/docker.asc
|
||||
mode: "0644"
|
||||
|
||||
- name: Add Docker apt repository
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [arch={{ docker_apt_arch }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
|
||||
ansible.builtin.deb822_repository:
|
||||
name: docker
|
||||
types: deb
|
||||
uris: "https://download.docker.com/linux/{{ ansible_distribution | lower }}"
|
||||
suites: "{{ ansible_distribution_release }}"
|
||||
components: stable
|
||||
architectures: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}"
|
||||
signed_by: "https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg"
|
||||
state: present
|
||||
filename: docker
|
||||
vars:
|
||||
docker_apt_arch: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}"
|
||||
|
||||
- name: Install Docker packages
|
||||
ansible.builtin.apt:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
gitea_ssh_host: git.aimloperations.com
|
||||
gitea_ssh_port: 30009
|
||||
gitea_key_path: "/home/{{ admin_user }}/.ssh/id_ed25519"
|
||||
# Any repo the deploy user should be able to read; used only as an access probe.
|
||||
gitea_test_repo: "ai_ml_operations/company_site.git"
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
# Per-server SSH key for cloning from Gitea.
|
||||
# 1. Generate an ed25519 key for the deploy user (if absent).
|
||||
# 2. Configure SSH for the Gitea host (port + identity).
|
||||
# 3. Test access first; only surface the "add this key" step when it's missing.
|
||||
|
||||
- name: gitea-key | ensure .ssh dir
|
||||
ansible.builtin.file:
|
||||
path: "/home/{{ admin_user }}/.ssh"
|
||||
state: directory
|
||||
owner: "{{ admin_user }}"
|
||||
group: "{{ admin_user }}"
|
||||
mode: "0700"
|
||||
|
||||
- name: gitea-key | generate deploy key
|
||||
become_user: "{{ admin_user }}"
|
||||
ansible.builtin.command:
|
||||
cmd: "ssh-keygen -t ed25519 -N '' -f {{ gitea_key_path }} -C '{{ admin_user }}@{{ inventory_hostname }}-gitea'"
|
||||
creates: "{{ gitea_key_path }}"
|
||||
|
||||
- name: gitea-key | configure SSH for Gitea host
|
||||
become_user: "{{ admin_user }}"
|
||||
ansible.builtin.blockinfile:
|
||||
path: "/home/{{ admin_user }}/.ssh/config"
|
||||
create: true
|
||||
owner: "{{ admin_user }}"
|
||||
group: "{{ admin_user }}"
|
||||
mode: "0600"
|
||||
marker: "# {mark} ANSIBLE MANAGED gitea"
|
||||
block: |
|
||||
Host {{ gitea_ssh_host }}
|
||||
User git
|
||||
Port {{ gitea_ssh_port }}
|
||||
IdentityFile {{ gitea_key_path }}
|
||||
IdentitiesOnly yes
|
||||
StrictHostKeyChecking accept-new
|
||||
|
||||
- name: gitea-key | read public key
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ gitea_key_path }}.pub"
|
||||
register: _gitea_pubkey
|
||||
|
||||
- name: gitea-key | check Gitea access (permission probe)
|
||||
become_user: "{{ admin_user }}"
|
||||
ansible.builtin.command:
|
||||
cmd: "git ls-remote {{ git_base_url }}/{{ gitea_test_repo }}"
|
||||
register: _gitea_access
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: gitea-key | access OK
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ inventory_hostname }} already has Gitea access; nothing to add."
|
||||
when: _gitea_access.rc == 0
|
||||
|
||||
- name: gitea-key | ADD THIS KEY to Gitea (access missing)
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
================= ACTION REQUIRED on {{ inventory_hostname }} =================
|
||||
This server cannot reach Gitea yet. Add its public key:
|
||||
|
||||
{{ _gitea_pubkey.content | b64decode | trim }}
|
||||
|
||||
Where (either works):
|
||||
* User key : Gitea > Settings > SSH / GPG Keys > Add Key
|
||||
* Deploy key: repo > Settings > Deploy Keys (per repo, read-only)
|
||||
Then re-run provisioning to continue.
|
||||
==============================================================================
|
||||
when: _gitea_access.rc != 0
|
||||
|
||||
- name: gitea-key | fail until key is added
|
||||
ansible.builtin.fail:
|
||||
msg: "No Gitea access from {{ inventory_hostname }}. Add the key shown above, then re-run."
|
||||
when: _gitea_access.rc != 0
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
# Node.js major version to install from NodeSource (provides npm + npx).
|
||||
node_major: 22
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
- name: Add NodeSource apt repository
|
||||
ansible.builtin.deb822_repository:
|
||||
name: nodesource
|
||||
types: deb
|
||||
uris: "https://deb.nodesource.com/node_{{ node_major }}.x"
|
||||
suites: nodistro
|
||||
components: main
|
||||
signed_by: "https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key"
|
||||
state: present
|
||||
register: _nodesource_repo
|
||||
|
||||
- name: Force NodeSource over the distro nodejs/npm
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/preferences.d/nodesource.pref
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
Package: *
|
||||
Pin: origin "deb.nodesource.com"
|
||||
Pin-Priority: 1001
|
||||
register: _nodesource_pin
|
||||
|
||||
- name: Refresh apt cache after NodeSource changes
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
when: _nodesource_repo is changed or _nodesource_pin is changed
|
||||
|
||||
- name: Remove distro nodejs/npm (NodeSource nodejs bundles its own npm + npx)
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- npm
|
||||
- nodejs
|
||||
state: absent
|
||||
purge: true
|
||||
|
||||
- name: Install Node.js from NodeSource (includes npm + npx)
|
||||
ansible.builtin.apt:
|
||||
name: nodejs
|
||||
state: latest
|
||||
allow_downgrade: true
|
||||
update_cache: true
|
||||
|
||||
- name: Verify node / npm / npx available
|
||||
ansible.builtin.command:
|
||||
cmd: "{{ item }} --version"
|
||||
loop:
|
||||
- node
|
||||
- npm
|
||||
- npx
|
||||
changed_when: false
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Install Tianji monitoring reporter
|
||||
ansible.builtin.shell: |
|
||||
curl -o- "{{ tianji_install_script_url }}" | bash
|
||||
args:
|
||||
creates: /usr/lib/systemd/system/tianji-reporter.service
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
web_static_dir: "{{ apps_base_dir }}/web-static"
|
||||
web_static_image: nginx:alpine
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
# Serves node/vite static builds (dta_webapp beta/prod) via a single nginx
|
||||
# container. Skips itself on hosts that have no node-static apps.
|
||||
|
||||
- name: web-static | node-static app names
|
||||
ansible.builtin.set_fact:
|
||||
_node_names: "{{ app_catalog | dict2items | selectattr('value.type', 'equalto', 'node-static') | map(attribute='key') | list }}"
|
||||
|
||||
- name: web-static | this host's static apps
|
||||
ansible.builtin.set_fact:
|
||||
_static_apps: "{{ host_apps | default([]) | selectattr('name', 'in', _node_names) | list }}"
|
||||
|
||||
- name: web-static | configure and run
|
||||
when: _static_apps | length > 0
|
||||
block:
|
||||
- name: web-static | ensure project dir
|
||||
ansible.builtin.file:
|
||||
path: "{{ web_static_dir }}"
|
||||
state: directory
|
||||
owner: "{{ admin_user }}"
|
||||
group: "{{ admin_user }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: web-static | nginx config
|
||||
ansible.builtin.template:
|
||||
src: nginx.conf.j2
|
||||
dest: "{{ web_static_dir }}/nginx.conf"
|
||||
owner: "{{ admin_user }}"
|
||||
group: "{{ admin_user }}"
|
||||
mode: "0644"
|
||||
register: _nginx_conf
|
||||
|
||||
- name: web-static | compose file
|
||||
ansible.builtin.template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ web_static_dir }}/docker-compose.yml"
|
||||
owner: "{{ admin_user }}"
|
||||
group: "{{ admin_user }}"
|
||||
mode: "0644"
|
||||
register: _compose_file
|
||||
|
||||
- name: web-static | start container
|
||||
ansible.builtin.command:
|
||||
cmd: "docker compose up -d --remove-orphans{{ ' --force-recreate' if (_compose_file is changed) else '' }}"
|
||||
chdir: "{{ web_static_dir }}"
|
||||
become: true
|
||||
become_user: "{{ admin_user }}"
|
||||
changed_when: true
|
||||
|
||||
- name: web-static | reload nginx on config change
|
||||
ansible.builtin.command:
|
||||
cmd: "docker compose exec -T web-static nginx -s reload"
|
||||
chdir: "{{ web_static_dir }}"
|
||||
become: true
|
||||
become_user: "{{ admin_user }}"
|
||||
when: _nginx_conf is changed and _compose_file is not changed
|
||||
changed_when: true
|
||||
@@ -0,0 +1,12 @@
|
||||
# Managed by Ansible (roles/web-static). Do not edit by hand.
|
||||
services:
|
||||
web-static:
|
||||
image: {{ web_static_image }}
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
{% for a in _static_apps %}
|
||||
- "{{ a.port }}:{{ a.port }}"
|
||||
{% endfor %}
|
||||
volumes:
|
||||
- {{ web_static_root }}:{{ web_static_root }}:ro
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
@@ -0,0 +1,19 @@
|
||||
# Managed by Ansible (roles/web-static). Do not edit by hand.
|
||||
{% for a in _static_apps %}
|
||||
server {
|
||||
listen {{ a.port }};
|
||||
server_name _;
|
||||
|
||||
root {{ (app_catalog[a.name].webroot_pattern | default(web_static_root ~ '/{env}_' ~ a.name)) | replace('{env}', a.env) }};
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ~* \.(?:js|css|woff2?|png|jpg|jpeg|gif|svg|ico)$ {
|
||||
expires 7d;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
}
|
||||
{% endfor %}
|
||||
Reference in New Issue
Block a user