refactor: move Ansible roles to standard ansible/roles/ location

This commit is contained in:
2026-02-25 20:46:33 -06:00
parent 74172c4e5a
commit 86708542c9
40 changed files with 1 additions and 1 deletions

View File

@@ -0,0 +1,202 @@
---
# ansible/roles/haproxy/tasks/certbot-cloudflare.yml
- name: Install Certbot and Cloudflare DNS plugin
ansible.builtin.package:
name:
- certbot
- python3-certbot-dns-cloudflare
state: present
- name: Create Let's Encrypt configuration directory
ansible.builtin.file:
path: /etc/letsencrypt
state: directory
owner: root
group: root
mode: '0755'
- name: Create Cloudflare credentials file
ansible.builtin.template:
src: cloudflare-credentials.ini.j2
dest: "{{ haproxy_certbot_cloudflare_credentials_file }}"
owner: root
group: root
mode: '0600'
no_log: true
- name: Create Certbot post-renewal hook script
ansible.builtin.template:
src: certbot-post-hook.sh.j2
dest: "{{ haproxy_certbot_post_hook }}"
owner: root
group: root
mode: '0755'
- name: Create Certbot renewal hooks directory
ansible.builtin.file:
path: /etc/letsencrypt/renewal-hooks/deploy
state: directory
mode: '0755'
- name: Link post-hook to renewal hooks directory
ansible.builtin.file:
src: "{{ haproxy_certbot_post_hook }}"
dest: /etc/letsencrypt/renewal-hooks/deploy/haproxy-reload.sh
state: link
force: true
- name: Check for existing certificates
ansible.builtin.stat:
path: "{{ haproxy_certbot_cert_dir }}/{{ item.domain | regex_replace('\\*\\.', '') }}"
register: existing_certs
loop: "{{ haproxy_certbot_domains }}"
loop_control:
label: "{{ item.domain }}"
- name: Build certificate request commands
ansible.builtin.set_fact:
certbot_commands: "{{ certbot_commands | default([]) + [command_item] }}"
vars:
cert_name: "{{ item.item.domain | regex_replace('\\*\\.', '') }}"
domain_list: >-
{{ [item.item.domain] +
(item.item.include_base | default(false) | ternary(
[item.item.domain | regex_replace('\\*\\.', '')],
[]
))
}}
command_item:
domain: "{{ item.item.domain }}"
cert_name: "{{ cert_name }}"
domains: "{{ domain_list }}"
exists: "{{ item.stat.exists }}"
loop: "{{ existing_certs.results }}"
loop_control:
label: "{{ item.item.domain }}"
- name: Display certificate request plan
ansible.builtin.debug:
msg: |
Will request certificates for:
{% for cmd in certbot_commands %}
- {{ cmd.cert_name }} ({{ cmd.domains | join(', ') }}) - {{ 'EXISTS' if cmd.exists else 'NEW' }}
{% endfor %}
- name: Request certificates from Let's Encrypt via Cloudflare DNS
ansible.builtin.command: >
certbot certonly
--non-interactive
--agree-tos
--email {{ haproxy_certbot_email }}
--dns-cloudflare
--dns-cloudflare-credentials {{ haproxy_certbot_cloudflare_credentials_file }}
--dns-cloudflare-propagation-seconds {{ haproxy_certbot_dns_propagation_seconds }}
--cert-name {{ item.cert_name }}
{% for domain in item.domains %}
--domain {{ domain }}
{% endfor %}
{% if haproxy_certbot_staging %}
--staging
{% endif %}
--deploy-hook {{ haproxy_certbot_post_hook }}
loop: "{{ certbot_commands }}"
loop_control:
label: "{{ item.cert_name }}"
when: not item.exists
register: certbot_result
failed_when:
- certbot_result.rc != 0
- "'Certificate not yet due for renewal' not in certbot_result.stdout"
- "'too many certificates already issued' not in certbot_result.stderr"
- name: Display certificate request results
ansible.builtin.debug:
msg: "Certificate for {{ item.item.cert_name }}: {{ 'SUCCESS' if item.rc == 0 else 'SKIPPED/FAILED' }}"
loop: "{{ certbot_result.results }}"
loop_control:
label: "{{ item.item.cert_name }}"
when: certbot_result.results is defined
- name: Run post-hook to combine certificates for HAProxy
ansible.builtin.command: "{{ haproxy_certbot_post_hook }}"
changed_when: true
when: certbot_result.changed
- name: Setup automatic certificate renewal via cron
ansible.builtin.cron:
name: "Certbot certificate renewal"
minute: "{{ haproxy_certbot_renewal_cron_minute }}"
hour: "{{ haproxy_certbot_renewal_cron_hour }}"
day: "{{ haproxy_certbot_renewal_cron_day }}"
job: "/usr/bin/certbot renew --quiet --deploy-hook {{ haproxy_certbot_post_hook }} 2>&1 | logger -t certbot"
user: root
state: present
- name: Create systemd timer for certificate renewal (alternative to cron)
when: ansible_service_mgr == "systemd"
block:
- name: Create certbot renewal service
ansible.builtin.copy:
content: |
[Unit]
Description=Certbot Renewal
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook {{ haproxy_certbot_post_hook }}
StandardOutput=journal
StandardError=journal
dest: /etc/systemd/system/certbot-renewal.service
mode: '0644'
- name: Create certbot renewal timer
ansible.builtin.copy:
content: |
[Unit]
Description=Certbot Renewal Timer
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
dest: /etc/systemd/system/certbot-renewal.timer
mode: '0644'
- name: Enable and start certbot renewal timer
ansible.builtin.systemd:
name: certbot-renewal.timer
enabled: true
state: started
daemon_reload: true
- name: Test certificate renewal (dry-run)
ansible.builtin.command: certbot renew --dry-run --dns-cloudflare --dns-cloudflare-credentials {{ haproxy_certbot_cloudflare_credentials_file }}
register: renewal_test
changed_when: false
failed_when: false
- name: Display renewal test results
ansible.builtin.debug:
msg: |
Certificate renewal dry-run: {{ 'SUCCESS ✓' if renewal_test.rc == 0 else 'FAILED ✗' }}
{% if renewal_test.rc == 0 %}
Your certificates will renew automatically.
{% else %}
Please check the output above for errors.
{% endif %}
- name: List all certificates
ansible.builtin.command: certbot certificates
register: cert_list
changed_when: false
- name: Display certificate information
ansible.builtin.debug:
msg: "{{ cert_list.stdout_lines }}"

View File

@@ -0,0 +1,78 @@
---
# ansible/roles/haproxy/tasks/certificates.yml
- name: Check if default certificate exists
ansible.builtin.stat:
path: "{{ haproxy_ssl_default_crt }}"
register: default_cert
- name: Generate self-signed certificate for development
when: not default_cert.stat.exists
block:
- name: Generate private key
openssl_privatekey:
path: "{{ haproxy_ssl_cert_dir }}/default.key"
size: 2048
mode: '0600'
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
- name: Generate certificate signing request
openssl_csr:
path: "{{ haproxy_ssl_cert_dir }}/default.csr"
privatekey_path: "{{ haproxy_ssl_cert_dir }}/default.key"
common_name: "lightning-lane.local.mk-labs.cloud"
subject_alt_name:
- "DNS:lightning-lane.local.mk-labs.cloud"
- "DNS:*.local.mk-labs.cloud"
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
- name: Generate self-signed certificate
openssl_certificate:
path: "{{ haproxy_ssl_cert_dir }}/default.crt"
privatekey_path: "{{ haproxy_ssl_cert_dir }}/default.key"
csr_path: "{{ haproxy_ssl_cert_dir }}/default.csr"
provider: selfsigned
selfsigned_not_after: "+3650d"
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
- name: Combine certificate and key for HAProxy
ansible.builtin.shell: |
cat {{ haproxy_ssl_cert_dir }}/default.crt \
{{ haproxy_ssl_cert_dir }}/default.key \
> {{ haproxy_ssl_default_crt }}
args:
creates: "{{ haproxy_ssl_default_crt }}"
- name: Set permissions on combined certificate
ansible.builtin.file:
path: "{{ haproxy_ssl_default_crt }}"
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: '0600'
- name: Create certificate directory structure
ansible.builtin.file:
path: "{{ haproxy_ssl_cert_dir }}/{{ item }}"
state: directory
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: '0755'
loop:
- live
- archive
- name: Deploy additional certificates
when: haproxy_ssl_certificates is defined
block:
- name: Copy certificate files
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ haproxy_ssl_cert_dir }}/live/{{ item.name }}.pem"
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: '0600'
loop: "{{ haproxy_ssl_certificates }}"
notify: Reload haproxy

View File

@@ -0,0 +1,94 @@
---
# ansible/roles/haproxy/tasks/configure.yml
- name: Fetch services from NetBox
when: haproxy_netbox_integration
block:
- name: Query NetBox for service configurations
ansible.builtin.uri:
url: "{{ haproxy_netbox_url }}/api/ipam/services/"
method: GET
headers:
Authorization: "Token {{ haproxy_netbox_token }}"
Accept: "application/json"
return_content: true
register: netbox_services
changed_when: false
- name: Parse NetBox services
ansible.builtin.set_fact:
haproxy_frontends: "{{ haproxy_frontends | default([]) + netbox_parsed_frontends }}"
haproxy_backends: "{{ haproxy_backends | default([]) + netbox_parsed_backends }}"
vars:
netbox_parsed_frontends: "{{ netbox_services.json.results | map(attribute='custom_fields') | list }}"
netbox_parsed_backends: "{{ netbox_services.json.results | map(attribute='custom_fields') | list }}"
when: netbox_services.json.results is defined
- name: Generate HAProxy main configuration
ansible.builtin.template:
src: haproxy.cfg.j2
dest: "{{ haproxy_config_dir }}/haproxy.cfg"
owner: root
group: root
mode: '0644'
backup: true
validate: 'haproxy -c -f %s'
notify: reload haproxy
- name: Configure rsyslog for HAProxy
when: haproxy_rsyslog_config
block:
- name: Create rsyslog configuration for HAProxy
ansible.builtin.copy:
content: |
# HAProxy logging configuration
$ModLoad imudp
$UDPServerRun 514
# Create separate log files for HAProxy
local0.* {{ haproxy_log_dir }}/haproxy.log
local0.info {{ haproxy_log_dir }}/haproxy-info.log
local0.notice {{ haproxy_log_dir }}/haproxy-notice.log
local0.err {{ haproxy_log_dir }}/haproxy-error.log
# Stop processing HAProxy logs
& stop
dest: /etc/rsyslog.d/49-haproxy.conf
mode: '0644'
notify: restart rsyslog
- name: Configure logrotate for HAProxy
when: haproxy_log_rotation
ansible.builtin.template:
src: logrotate.j2
dest: /etc/logrotate.d/haproxy
mode: '0644'
- name: Create HAProxy systemd override directory
ansible.builtin.file:
path: /etc/systemd/system/haproxy.service.d
state: directory
mode: '0755'
- name: Configure HAProxy systemd service overrides
ansible.builtin.copy:
content: |
[Service]
# Increase file descriptor limits
LimitNOFILE=65536
# Runtime directory
RuntimeDirectory=haproxy
RuntimeDirectoryMode=0755
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths={{ haproxy_log_dir }}
dest: /etc/systemd/system/haproxy.service.d/override.conf
mode: '0644'
notify:
- Reload systemd
- Restart haproxy

View File

@@ -0,0 +1,128 @@
---
# ansible/roles/haproxy/tasks/install.yml
- name: Install HAProxy repository (Ubuntu)
when: ansible_os_family == "Debian"
block:
- name: Add HAProxy PPA
ansible.builtin.apt_repository:
repo: "ppa:vbernat/haproxy-{{ haproxy_version }}"
state: present
update_cache: true
- name: Install HAProxy
ansible.builtin.apt:
name: haproxy
state: present
update_cache: true
- name: Install HAProxy (Fedora/RHEL)
when: ansible_os_family == "RedHat"
block:
- name: Install HAProxy
ansible.builtin.dnf:
name: haproxy
state: present
- name: Install additional packages
ansible.builtin.package:
name:
- rsyslog
- logrotate
- socat # For HAProxy socket communication
state: present
- name: Create HAProxy system user
ansible.builtin.user:
name: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
system: true
shell: /usr/sbin/nologin
home: "{{ haproxy_chroot_dir }}"
create_home: true
- name: Create required directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: '0755'
loop:
- "{{ haproxy_config_dir }}"
- "{{ haproxy_ssl_cert_dir }}"
- "{{ haproxy_log_dir }}"
- "{{ haproxy_chroot_dir }}"
- /var/lib/haproxy/dev
- name: Create HAProxy socket directory
ansible.builtin.file:
path: /run/haproxy
state: directory
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: '0755'
- name: Configure firewall rules
when: haproxy_configure_firewall
block:
- name: Open HAProxy ports (UFW)
when: ansible_os_family == "Debian"
community.general.ufw:
rule: allow
port: "{{ item.split('/')[0] }}"
proto: "{{ item.split('/')[1] }}"
loop: "{{ haproxy_firewall_allowed_ports }}"
- name: Open HAProxy ports (firewalld)
when: ansible_os_family == "RedHat"
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true
state: enabled
immediate: true
loop: "{{ haproxy_firewall_allowed_ports }}"
- name: Install Prometheus HAProxy exporter
when: haproxy_prometheus_exporter
block:
- name: Download HAProxy exporter
ansible.builtin.get_url:
url: "https://github.com/prometheus/haproxy_exporter/releases/download/v0.15.0/haproxy_exporter-0.15.0.linux-amd64.tar.gz"
dest: /tmp/haproxy_exporter.tar.gz
mode: '0644'
- name: Extract HAProxy exporter
ansible.builtin.unarchive:
src: /tmp/haproxy_exporter.tar.gz
dest: /usr/local/bin/
remote_src: true
extra_opts:
- --strip-components=1
- --wildcards
- '*/haproxy_exporter'
- name: Create HAProxy exporter systemd service
ansible.builtin.copy:
content: |
[Unit]
Description=HAProxy Exporter
After=network.target
[Service]
Type=simple
User={{ haproxy_user }}
ExecStart=/usr/local/bin/haproxy_exporter --haproxy.scrape-uri=unix:/run/haproxy/admin.sock
Restart=on-failure
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/haproxy-exporter.service
mode: '0644'
- name: Enable and start HAProxy exporter
ansible.builtin.systemd:
name: haproxy-exporter
state: started
enabled: true
daemon_reload: true

View File

@@ -0,0 +1,40 @@
---
# ansible/roles/haproxy/tasks/main.yml
# - name: Include OS-specific variables
# include_vars: "{{ ansible_os_family }}.yml"
# tags: [haproxy, always]
- name: Include installation tasks
ansible.builtin.include_tasks: install.yml
tags: [haproxy, install]
- name: Include Certbot with Cloudflare DNS tasks
ansible.builtin.include_tasks: certbot-cloudflare.yml
when:
- haproxy_certbot_enable
- haproxy_certbot_challenge_method == "dns-cloudflare"
tags: [haproxy, certificates, certbot, cloudflare]
- name: Include fallback self-signed certificate tasks
ansible.builtin.include_tasks: certificates.yml
when:
- not haproxy_certbot_enable
- haproxy_certbot_fallback_selfsigned | default(false)
tags: [haproxy, certificates, selfsigned]
- name: Include configuration tasks
ansible.builtin.include_tasks: configure.yml
tags: [haproxy, configure]
- name: Include validation tasks
ansible.builtin.include_tasks: validate.yml
tags: [haproxy, validate]
- name: Ensure HAProxy is started and enabled
ansible.builtin.systemd:
name: haproxy
state: started
enabled: true
daemon_reload: true
tags: [haproxy, service]

View File

@@ -0,0 +1,47 @@
---
# ansible/roles/haproxy/tasks/validate.yml
- name: Validate HAProxy configuration syntax
command: haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg
register: haproxy_validation
changed_when: false
failed_when: haproxy_validation.rc != 0
- name: Display validation results
debug:
msg: "{{ haproxy_validation.stdout_lines }}"
when: haproxy_validation.stdout_lines is defined
- name: Check HAProxy service status
systemd:
name: haproxy
state: started
register: haproxy_status
changed_when: false
- name: Verify HAProxy is listening on configured ports
wait_for:
host: "{{ ansible_default_ipv4.address }}"
port: "{{ item }}"
timeout: 10
loop:
- 80
- 443
- "{{ haproxy_stats_port }}"
when: haproxy_status.status.ActiveState == "active"
- name: Test stats interface accessibility
uri:
url: "http://localhost:{{ haproxy_stats_port }}{{ haproxy_stats_uri }}"
user: "{{ haproxy_stats_username }}"
password: "{{ haproxy_stats_password }}"
force_basic_auth: true
status_code: 200
register: stats_test
changed_when: false
failed_when: false
- name: Display stats interface status
debug:
msg: "Stats interface is {{ 'accessible' if stats_test.status == 200 else 'not accessible' }}"