2026-04-19 22:05:19 -04:00
|
|
|
- name: Deploy n8n
|
|
|
|
|
hosts: all
|
|
|
|
|
become: true
|
|
|
|
|
|
|
|
|
|
vars:
|
|
|
|
|
stack_dir: "/opt/stacks/{{ stack_name }}"
|
|
|
|
|
|
|
|
|
|
pre_tasks:
|
|
|
|
|
- name: Fail if stack_name is not set
|
|
|
|
|
fail:
|
|
|
|
|
msg: "stack_name is required"
|
|
|
|
|
when: stack_name is not defined or stack_name | length == 0
|
|
|
|
|
|
|
|
|
|
- name: Fail if n8n_url is not set
|
|
|
|
|
fail:
|
|
|
|
|
msg: "n8n_url is required"
|
|
|
|
|
when: n8n_url is not defined or n8n_url | length == 0
|
|
|
|
|
|
|
|
|
|
tasks:
|
2026-04-20 21:25:05 -04:00
|
|
|
# 🔎 Check if 1Password item exists
|
|
|
|
|
- name: Check if 1Password item exists
|
|
|
|
|
shell: |
|
2026-04-21 02:08:38 +00:00
|
|
|
op item get "{{ stack_name }}" --vault="semaphore-ansible"
|
2026-04-20 21:25:05 -04:00
|
|
|
register: op_check
|
|
|
|
|
failed_when: false
|
|
|
|
|
changed_when: false
|
2026-04-21 01:31:55 +00:00
|
|
|
check_mode: no
|
2026-04-21 01:41:43 +00:00
|
|
|
delegate_to: localhost
|
2026-04-21 01:56:22 +00:00
|
|
|
connection: local
|
2026-04-21 01:41:43 +00:00
|
|
|
become: false
|
|
|
|
|
no_log: true
|
2026-04-20 21:25:05 -04:00
|
|
|
environment:
|
2026-04-21 01:59:08 +00:00
|
|
|
OP_SERVICE_ACCOUNT_TOKEN: "{{ OP_SERVICE_ACCOUNT_TOKEN }}"
|
2026-04-20 21:25:05 -04:00
|
|
|
|
|
|
|
|
# 🔐 Load existing secrets
|
|
|
|
|
- name: Load secrets from 1Password
|
|
|
|
|
when: op_check.rc == 0
|
|
|
|
|
shell: |
|
2026-04-21 02:08:38 +00:00
|
|
|
op item get "{{ stack_name }}" --vault="semaphore-ansible" --format json
|
2026-04-20 21:25:05 -04:00
|
|
|
register: op_item
|
2026-04-21 01:31:55 +00:00
|
|
|
check_mode: no
|
2026-04-21 01:41:43 +00:00
|
|
|
delegate_to: localhost
|
2026-04-21 01:56:22 +00:00
|
|
|
connection: local
|
2026-04-21 01:41:43 +00:00
|
|
|
become: false
|
|
|
|
|
no_log: true
|
2026-04-20 21:25:05 -04:00
|
|
|
environment:
|
2026-04-21 01:59:08 +00:00
|
|
|
OP_SERVICE_ACCOUNT_TOKEN: "{{ OP_SERVICE_ACCOUNT_TOKEN }}"
|
2026-04-20 21:25:05 -04:00
|
|
|
|
|
|
|
|
- name: Set secrets from 1Password
|
|
|
|
|
when: op_check.rc == 0
|
2026-04-21 01:41:43 +00:00
|
|
|
no_log: true
|
2026-04-20 21:25:05 -04:00
|
|
|
set_fact:
|
|
|
|
|
n8n_postgres_password: "{{ (op_item.stdout | from_json).fields | selectattr('label','equalto','postgres_password') | map(attribute='value') | first }}"
|
|
|
|
|
n8n_encryption_key: "{{ (op_item.stdout | from_json).fields | selectattr('label','equalto','encryption_key') | map(attribute='value') | first }}"
|
|
|
|
|
|
|
|
|
|
# 🔑 Generate if new
|
|
|
|
|
- name: Generate postgres password
|
|
|
|
|
when: op_check.rc != 0
|
2026-04-21 01:41:43 +00:00
|
|
|
no_log: true
|
2026-04-20 21:25:05 -04:00
|
|
|
set_fact:
|
|
|
|
|
n8n_postgres_password: "{{ lookup('password','/dev/null length=32 chars=ascii_letters') }}"
|
|
|
|
|
|
|
|
|
|
- name: Generate encryption key
|
|
|
|
|
when: op_check.rc != 0
|
2026-04-21 01:41:43 +00:00
|
|
|
no_log: true
|
2026-04-20 21:25:05 -04:00
|
|
|
set_fact:
|
|
|
|
|
n8n_encryption_key: "{{ lookup('password','/dev/null length=64 chars=hexdigits') }}"
|
|
|
|
|
|
|
|
|
|
# 📦 Store in 1Password
|
|
|
|
|
- name: Create 1Password item
|
|
|
|
|
when: op_check.rc != 0
|
2026-04-21 01:41:43 +00:00
|
|
|
delegate_to: localhost
|
2026-04-21 01:56:22 +00:00
|
|
|
connection: local
|
2026-04-21 01:41:43 +00:00
|
|
|
become: false
|
|
|
|
|
no_log: true
|
2026-04-20 21:25:05 -04:00
|
|
|
shell: |
|
|
|
|
|
op item create \
|
|
|
|
|
--category="Server" \
|
|
|
|
|
--title="{{ stack_name }}" \
|
2026-04-21 02:08:38 +00:00
|
|
|
--vault="semaphore-ansible" \
|
2026-04-20 21:25:05 -04:00
|
|
|
"url=https://{{ n8n_url }}" \
|
|
|
|
|
"username={{ n8n_editor_email }}" \
|
|
|
|
|
"password={{ n8n_editor_password }}" \
|
|
|
|
|
"postgres_password={{ n8n_postgres_password }}" \
|
|
|
|
|
"encryption_key={{ n8n_encryption_key }}"
|
|
|
|
|
environment:
|
2026-04-21 01:59:08 +00:00
|
|
|
OP_SERVICE_ACCOUNT_TOKEN: "{{ OP_SERVICE_ACCOUNT_TOKEN }}"
|
2026-04-20 21:25:05 -04:00
|
|
|
|
|
|
|
|
# 🧱 Infrastructure
|
2026-04-19 22:05:19 -04:00
|
|
|
- name: Ensure stack directory exists
|
|
|
|
|
file:
|
|
|
|
|
path: "{{ stack_dir }}"
|
|
|
|
|
state: directory
|
|
|
|
|
mode: "0755"
|
|
|
|
|
|
|
|
|
|
- name: Ensure pangolin network exists
|
|
|
|
|
community.docker.docker_network:
|
|
|
|
|
name: pangolin
|
|
|
|
|
state: present
|
|
|
|
|
|
2026-04-20 21:25:05 -04:00
|
|
|
# 📄 Templates
|
2026-04-19 22:05:19 -04:00
|
|
|
- name: Render .env
|
|
|
|
|
template:
|
|
|
|
|
src: templates/n8n.env.j2
|
|
|
|
|
dest: "{{ stack_dir }}/.env"
|
|
|
|
|
mode: "0600"
|
|
|
|
|
|
|
|
|
|
- name: Render compose file
|
|
|
|
|
template:
|
|
|
|
|
src: templates/n8n-compose.yml.j2
|
|
|
|
|
dest: "{{ stack_dir }}/compose.yml"
|
|
|
|
|
|
2026-04-20 21:25:05 -04:00
|
|
|
# 🚀 Deploy
|
2026-04-19 22:05:19 -04:00
|
|
|
- name: Deploy stack
|
|
|
|
|
command: docker compose up -d
|
|
|
|
|
args:
|
2026-04-21 01:37:07 +00:00
|
|
|
chdir: "{{ stack_dir }}"
|
|
|
|
|
when: not ansible_check_mode
|