151 lines
5.0 KiB
YAML
151 lines
5.0 KiB
YAML
---
|
|
# Usage: ansible-playbook -i 'root@<droplet-ip>,' deploy/ansible/deploy.yml
|
|
# Pre: droplet cloud-init complete (.bootstrap-complete marker present)
|
|
|
|
- name: Deploy aibook-fun app layer
|
|
hosts: all
|
|
gather_facts: false
|
|
become: true
|
|
vars:
|
|
app_dir: /opt/aibook_fun
|
|
repo_dir: /opt/aibook_fun/repo
|
|
repo_url: "https://git.weown.tools/WeOwnAI/aibook-fun.git"
|
|
repo_branch: main
|
|
image_tag: "aibook-fun-marketing:v1"
|
|
|
|
pre_tasks:
|
|
- name: Verify droplet has completed cloud-init bootstrap
|
|
ansible.builtin.stat:
|
|
path: "{{ app_dir }}/.bootstrap-complete"
|
|
register: bootstrap_marker
|
|
|
|
- name: Fail clearly if bootstrap is not complete
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
{{ app_dir }}/.bootstrap-complete is missing — droplet has not
|
|
finished cloud-init. Wait a few minutes after `tofu apply` and retry.
|
|
Check /var/log/cloud-init-output.log on the droplet.
|
|
when: not bootstrap_marker.stat.exists
|
|
|
|
- name: Ensure app dir exists
|
|
ansible.builtin.file:
|
|
path: "{{ app_dir }}"
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
tasks:
|
|
# ─── Clone / update repo on droplet ────────────────────────────────────
|
|
- name: Clone (or update) aibook-fun repo on droplet
|
|
ansible.builtin.git:
|
|
repo: "{{ repo_url }}"
|
|
dest: "{{ repo_dir }}"
|
|
version: "{{ repo_branch }}"
|
|
force: true
|
|
register: git_clone
|
|
notify: Rebuild image
|
|
|
|
# ─── Upload Dockerfile (deploy/ scaffold not yet pushed to git.weown.tools) ─
|
|
- name: Upload Dockerfile to droplet
|
|
ansible.builtin.copy:
|
|
src: ../docker/Dockerfile
|
|
dest: "{{ app_dir }}/Dockerfile"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
|
|
# ─── Build docker image locally on droplet (unconditional — docker layer cache handles no-ops) ─
|
|
- name: Build Next.js docker image locally
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
docker build -t {{ image_tag }} -f {{ app_dir }}/Dockerfile {{ repo_dir }}/marketing-site/
|
|
args:
|
|
executable: /bin/bash
|
|
register: image_build
|
|
changed_when: "'writing image' in image_build.stderr"
|
|
notify: Reconcile docker compose stack
|
|
|
|
# ─── Upload compose + Caddyfile ────────────────────────────────────────
|
|
- name: Upload compose.yaml
|
|
ansible.builtin.copy:
|
|
src: ../docker/compose.prod.yaml
|
|
dest: "{{ app_dir }}/compose.yaml"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
notify: Reconcile docker compose stack
|
|
|
|
- name: Upload Caddyfile
|
|
ansible.builtin.copy:
|
|
src: ../docker/Caddyfile
|
|
dest: "{{ app_dir }}/Caddyfile"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
notify: Reconcile docker compose stack
|
|
|
|
- name: Ensure caddy log dir is writable
|
|
ansible.builtin.file:
|
|
path: /var/log/caddy
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0777"
|
|
|
|
- name: Ensure Caddy Docker volumes are writable (caddy runs as UID 1000)
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
for suffix in caddy_data caddy_config; do
|
|
path="/var/lib/docker/volumes/aibook_fun_${suffix}/_data"
|
|
if [ -d "$path" ]; then
|
|
chown -R 1000:1000 "$path"
|
|
fi
|
|
done
|
|
args:
|
|
executable: /bin/bash
|
|
changed_when: false
|
|
|
|
# ─── Ensure compose stack is running (idempotent) ──────────────────────
|
|
- name: Ensure compose stack is running (idempotent — `up -d` no-op if unchanged)
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
cd {{ app_dir }}
|
|
docker compose up -d --remove-orphans
|
|
args:
|
|
executable: /bin/bash
|
|
register: compose_up
|
|
changed_when: "'Started' in compose_up.stderr or 'Created' in compose_up.stderr or 'Recreated' in compose_up.stderr"
|
|
|
|
- name: Wait for web app to respond
|
|
ansible.builtin.uri:
|
|
url: http://127.0.0.1:3000/
|
|
status_code: [200]
|
|
register: health_probe
|
|
retries: 20
|
|
delay: 5
|
|
until: health_probe.status == 200
|
|
|
|
- name: Report success
|
|
ansible.builtin.debug:
|
|
msg: "aibook-fun app layer deployed. URL: https://aibook.fun"
|
|
|
|
handlers:
|
|
- name: Rebuild image
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
cd {{ repo_dir }}
|
|
docker build -t {{ image_tag }} -f {{ repo_dir }}/deploy/docker/Dockerfile {{ repo_dir }}/marketing-site/
|
|
args:
|
|
executable: /bin/bash
|
|
changed_when: false
|
|
|
|
- name: Reconcile docker compose stack
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
cd {{ app_dir }}
|
|
docker compose up -d --force-recreate --remove-orphans
|
|
args:
|
|
executable: /bin/bash
|
|
changed_when: false
|