wulf-ansible/playbooks/n8n.yml
lorentz b7196c3fc6 Run op CLI on controller and silence secret output
The target host does not have the op CLI installed, so op invocations
must run on the Semaphore controller (delegate_to: localhost, become:
false — the controller runs rootless). Generated/loaded secrets also
leak into task logs on failure, so mark the three op tasks and the
set_fact tasks with no_log: true.
2026-04-21 01:41:43 +00:00

115 lines
No EOL
3.4 KiB
YAML

- 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:
# 🔎 Check if 1Password item exists
- name: Check if 1Password item exists
shell: |
op item get "{{ stack_name }}" --vault="Automation"
register: op_check
failed_when: false
changed_when: false
check_mode: no
delegate_to: localhost
become: false
no_log: true
environment:
OP_SERVICE_ACCOUNT_TOKEN: "{{ lookup('env','OP_SERVICE_ACCOUNT_TOKEN') }}"
# 🔐 Load existing secrets
- name: Load secrets from 1Password
when: op_check.rc == 0
shell: |
op item get "{{ stack_name }}" --vault="Automation" --format json
register: op_item
check_mode: no
delegate_to: localhost
become: false
no_log: true
environment:
OP_SERVICE_ACCOUNT_TOKEN: "{{ lookup('env','OP_SERVICE_ACCOUNT_TOKEN') }}"
- name: Set secrets from 1Password
when: op_check.rc == 0
no_log: true
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
no_log: true
set_fact:
n8n_postgres_password: "{{ lookup('password','/dev/null length=32 chars=ascii_letters') }}"
- name: Generate encryption key
when: op_check.rc != 0
no_log: true
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
delegate_to: localhost
become: false
no_log: true
shell: |
op item create \
--category="Server" \
--title="{{ stack_name }}" \
--vault="Automation" \
"url=https://{{ n8n_url }}" \
"username={{ n8n_editor_email }}" \
"password={{ n8n_editor_password }}" \
"postgres_password={{ n8n_postgres_password }}" \
"encryption_key={{ n8n_encryption_key }}"
environment:
OP_SERVICE_ACCOUNT_TOKEN: "{{ lookup('env','OP_SERVICE_ACCOUNT_TOKEN') }}"
# 🧱 Infrastructure
- 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
# 📄 Templates
- 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"
# 🚀 Deploy
- name: Deploy stack
command: docker compose up -d
args:
chdir: "{{ stack_dir }}"
when: not ansible_check_mode