refactor: consolidate all roles into ansible/roles/ and update ansible.cfg
- Move all roles from playbooks/roles/ to roles/ - Update roles_path in ansible.cfg - Add cast user to common role - Create standalone podman role - Add semaphore role with Podman + Quadlet support
This commit is contained in:
6
ansible/playbooks/deploy_semaphore.yml
Normal file
6
ansible/playbooks/deploy_semaphore.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Deploy Semaphore on figment
|
||||
hosts: figment
|
||||
become: true
|
||||
roles:
|
||||
- semaphore
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/authentik/defaults/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
authentik_base_dir: /opt/docker/authentik
|
||||
|
||||
# All secrets MUST be set in vault
|
||||
authentik_secret_key: ""
|
||||
authentik_admin_password: ""
|
||||
authentik_database_password: ""
|
||||
@@ -1,8 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/authentik/handlers/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: restart authentik
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ authentik_base_dir }}"
|
||||
state: restarted
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# FILE: roles/authentik/tasks/main.yml
|
||||
# DESCRIPTION: Deploys Authentik from boilerplate-generated files in the repo.
|
||||
# The compose.yaml lives in boilerplates/authentik/.
|
||||
# The .env file is templated to inject secrets from Ansible vault.
|
||||
# Custom blueprints are synced from boilerplates/authentik/blueprints/.
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Create Authentik directory structure
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: '0775'
|
||||
loop:
|
||||
- "{{ authentik_base_dir }}"
|
||||
- "{{ authentik_base_dir }}/blueprints"
|
||||
|
||||
- name: Copy compose.yaml from boilerplate
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/authentik/compose.yaml"
|
||||
dest: "{{ authentik_base_dir }}/compose.yaml"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: '0644'
|
||||
notify: restart authentik
|
||||
|
||||
- name: Sync custom blueprints
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/authentik/blueprints/"
|
||||
dest: "{{ authentik_base_dir }}/blueprints/"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: '0644'
|
||||
notify: restart authentik
|
||||
|
||||
- name: Deploy .env with secrets from vault
|
||||
ansible.builtin.template:
|
||||
src: env.j2
|
||||
dest: "{{ authentik_base_dir }}/.env"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: '0600'
|
||||
notify: restart authentik
|
||||
|
||||
- name: Start Authentik
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ authentik_base_dir }}"
|
||||
state: present
|
||||
@@ -1,13 +0,0 @@
|
||||
# Managed by Ansible — do not edit manually
|
||||
AUTHENTIK_SECRET_KEY={{ authentik_secret_key }}
|
||||
DATABASE_PASSWORD={{ authentik_database_password }}
|
||||
AUTHENTIK_ADMIN_PASSWORD={{ authentik_admin_password }}
|
||||
|
||||
# OIDC client credentials for blueprints
|
||||
# These are substituted into blueprint YAML via ${VAR} syntax
|
||||
PROXMOX_OIDC_CLIENT_ID={{ vault_proxmox_oidc_client_id }}
|
||||
PROXMOX_OIDC_CLIENT_SECRET={{ vault_proxmox_oidc_client_secret }}
|
||||
STEPCA_OIDC_CLIENT_ID={{ vault_stepca_oidc_client_id }}
|
||||
STEPCA_OIDC_CLIENT_SECRET={{ vault_stepca_oidc_client_secret }}
|
||||
GITEA_OIDC_CLIENT_ID={{ vault_gitea_oidc_client_id }}
|
||||
GITEA_OIDC_CLIENT_SECRET={{ vault_gitea_oidc_client_secret }}
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/common/defaults/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
common_timezone: America/Chicago
|
||||
|
||||
common_ntp_server: "sundial.local.mk-labs.cloud"
|
||||
|
||||
common_packages:
|
||||
- acl
|
||||
- curl
|
||||
- wget
|
||||
- vim
|
||||
- htop
|
||||
- git
|
||||
- jq
|
||||
- unzip
|
||||
- ca-certificates
|
||||
- gnupg
|
||||
- lsb-release
|
||||
- net-tools
|
||||
- dnsutils
|
||||
- lvm2
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# LVM root volume expansion
|
||||
# Override common_root_pv/vg/lv per host if the template uses different names.
|
||||
# Ubuntu 24.04 cloud templates use ubuntu-vg/ubuntu-lv by default.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
common_expand_root_lvm: true
|
||||
common_root_pv: /dev/sda3 # Ubuntu cloud-init partition layout
|
||||
common_root_vg: ubuntu-vg
|
||||
common_root_lv: ubuntu-lv
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/common/handlers/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: restart chrony
|
||||
systemd:
|
||||
name: chrony
|
||||
state: restarted
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/common/defaults/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
common_timezone: America/Chicago
|
||||
|
||||
common_ntp_server: sundial.local.mk-labs.cloud
|
||||
|
||||
common_packages:
|
||||
- curl
|
||||
- wget
|
||||
- vim
|
||||
- htop
|
||||
- git
|
||||
- jq
|
||||
- unzip
|
||||
- ca-certificates
|
||||
- gnupg
|
||||
- lsb-release
|
||||
- net-tools
|
||||
- dnsutils
|
||||
@@ -1,77 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/common/tasks/main.yml
|
||||
# DESCRIPTION: Baseline configuration applied to all managed Ubuntu hosts.
|
||||
# Handles hostname, timezone, core packages, NTP, ansible user,
|
||||
# and optional LVM root volume expansion.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Set hostname
|
||||
hostname:
|
||||
name: "{{ inventory_hostname | replace('_', '-') }}"
|
||||
|
||||
- name: Set timezone
|
||||
timezone:
|
||||
name: "{{ common_timezone }}"
|
||||
|
||||
- name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Install base utility packages
|
||||
apt:
|
||||
name: "{{ common_packages }}"
|
||||
state: present
|
||||
|
||||
- name: Install chrony
|
||||
apt:
|
||||
name: chrony
|
||||
state: present
|
||||
|
||||
- name: Configure chrony to use sundial
|
||||
template:
|
||||
src: chrony.conf.j2
|
||||
dest: /etc/chrony/chrony.conf
|
||||
mode: '0644'
|
||||
notify: restart chrony
|
||||
|
||||
- name: Ensure chrony is enabled and running
|
||||
systemd:
|
||||
name: chrony
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Ensure ansible user has passwordless sudo
|
||||
lineinfile:
|
||||
path: /etc/sudoers.d/{{ ansible_user }}
|
||||
line: "{{ ansible_user }} ALL=(ALL) NOPASSWD:ALL"
|
||||
create: yes
|
||||
mode: '0440'
|
||||
validate: 'visudo -cf %s'
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# LVM root volume expansion
|
||||
# Extends the root PV to the full disk size and grows the LV + filesystem.
|
||||
# This codifies what was previously done manually after provisioning.
|
||||
# Runs only when common_expand_root_lvm is true (default: true).
|
||||
# Safe to re-run — pvresize and lvextend are idempotent when already at max.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Expand root PV to full disk size
|
||||
command: pvresize {{ common_root_pv }}
|
||||
register: pvresize_result
|
||||
changed_when: "'changed' in pvresize_result.stdout or pvresize_result.rc == 0"
|
||||
when: common_expand_root_lvm | bool
|
||||
|
||||
- name: Extend root LV to 100% of free VG space
|
||||
lvol:
|
||||
vg: "{{ common_root_vg }}"
|
||||
lv: "{{ common_root_lv }}"
|
||||
size: +100%FREE
|
||||
resizefs: yes
|
||||
when:
|
||||
- common_expand_root_lvm | bool
|
||||
ignore_errors: yes
|
||||
# ignore_errors because lvextend returns non-zero when already at max size.
|
||||
# resizefs: yes handles the resize2fs call inline — no separate task needed.
|
||||
@@ -1,136 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/common/tasks/step_ca_client.yml
|
||||
# DESCRIPTION: Enroll host as a step-ca SSH client. Installs the step CLI,
|
||||
# bootstraps trust with the CA, configures sshd to accept
|
||||
# CA-signed user certificates, signs a host certificate, and
|
||||
# sets up automated renewal via systemd timer.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Install step CLI
|
||||
apt:
|
||||
deb: "https://dl.smallstep.com/cli/docs-ca-install/latest/step-cli_amd64.deb"
|
||||
state: present
|
||||
|
||||
- name: Bootstrap step-ca trust
|
||||
command: >
|
||||
step ca bootstrap
|
||||
--ca-url {{ step_ca_url }}
|
||||
--fingerprint {{ step_ca_fingerprint }}
|
||||
--install
|
||||
args:
|
||||
creates: /root/.step/certs/root_ca.crt
|
||||
|
||||
- name: Get SSH user CA public key
|
||||
command: step ssh config --roots
|
||||
register: ssh_user_ca_key
|
||||
changed_when: false
|
||||
|
||||
- name: Write SSH user CA public key
|
||||
copy:
|
||||
content: "{{ ssh_user_ca_key.stdout }}"
|
||||
dest: /etc/ssh/ssh_user_key.pub
|
||||
mode: '0644'
|
||||
notify: restart sshd
|
||||
|
||||
- name: Configure sshd to trust CA-signed user certificates
|
||||
lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
line: "TrustedUserCAKeys /etc/ssh/ssh_user_key.pub"
|
||||
regexp: "^#?TrustedUserCAKeys"
|
||||
state: present
|
||||
notify: restart sshd
|
||||
|
||||
# --- Host certificate signing ---
|
||||
|
||||
- name: Write provisioner password to temp file on controller
|
||||
copy:
|
||||
content: "{{ stepca_ca_password }}"
|
||||
dest: /tmp/step_provisioner_pw
|
||||
mode: '0600'
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
no_log: true
|
||||
|
||||
- name: Generate one-time host token
|
||||
command: >
|
||||
step ca token {{ inventory_hostname }}.local.mk-labs.cloud
|
||||
--ssh --host
|
||||
--provisioner {{ step_ca_provisioner_name }}
|
||||
--password-file /tmp/step_provisioner_pw
|
||||
delegate_to: localhost
|
||||
register: host_token
|
||||
changed_when: false
|
||||
|
||||
- name: Sign host certificate
|
||||
command: >
|
||||
step ssh certificate {{ inventory_hostname }}.local.mk-labs.cloud
|
||||
/etc/ssh/ssh_host_ecdsa_key.pub
|
||||
--host --sign
|
||||
--token {{ host_token.stdout }}
|
||||
--no-password --insecure
|
||||
args:
|
||||
creates: /etc/ssh/ssh_host_ecdsa_key-cert.pub
|
||||
notify: restart sshd
|
||||
|
||||
- name: Configure sshd to present host certificate
|
||||
blockinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
marker: "# {mark} STEP-CA HOST CERTIFICATE"
|
||||
block: |
|
||||
HostCertificate /etc/ssh/ssh_host_ecdsa_key-cert.pub
|
||||
HostKey /etc/ssh/ssh_host_ecdsa_key
|
||||
notify: restart sshd
|
||||
|
||||
- name: Clean up provisioner password from controller
|
||||
file:
|
||||
path: /tmp/step_provisioner_pw
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
no_log: true
|
||||
|
||||
# --- Automated renewal via systemd ---
|
||||
|
||||
- name: Create host cert renewal script
|
||||
copy:
|
||||
content: |
|
||||
#!/bin/bash
|
||||
step ssh renew --force /etc/ssh/ssh_host_ecdsa_key-cert.pub /etc/ssh/ssh_host_ecdsa_key
|
||||
systemctl restart sshd
|
||||
dest: /usr/local/bin/step-renew-host-cert.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Create systemd timer for host cert renewal
|
||||
copy:
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Renew step-ca SSH host certificate
|
||||
|
||||
[Timer]
|
||||
OnCalendar=weekly
|
||||
Persistent=true
|
||||
RandomizedDelaySec=3600
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
dest: /etc/systemd/system/step-renew-host-cert.timer
|
||||
mode: '0644'
|
||||
|
||||
- name: Create systemd service for host cert renewal
|
||||
copy:
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Renew step-ca SSH host certificate
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/step-renew-host-cert.sh
|
||||
dest: /etc/systemd/system/step-renew-host-cert.service
|
||||
mode: '0644'
|
||||
|
||||
- name: Enable and start renewal timer
|
||||
systemd:
|
||||
name: step-renew-host-cert.timer
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
@@ -1,5 +0,0 @@
|
||||
# Managed by Ansible — do not edit manually
|
||||
server {{ common_ntp_server }} iburst
|
||||
driftfile /var/lib/chrony/chrony.drift
|
||||
makestep 1.0 3
|
||||
rtcsync
|
||||
@@ -1,59 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/control-plane/tasks/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
- name: Check if cluster is already initialized
|
||||
stat:
|
||||
path: /etc/kubernetes/admin.conf
|
||||
register: kube_init_stat
|
||||
|
||||
- name: Initialize the Kubernetes cluster
|
||||
when: not kube_init_stat.stat.exists
|
||||
command: >
|
||||
kubeadm init
|
||||
--pod-network-cidr=10.244.0.0/16
|
||||
--control-plane-endpoint={{ inventory_hostname }}
|
||||
register: kubeadm_init
|
||||
|
||||
- name: Create .kube directory for the user
|
||||
become: no
|
||||
file:
|
||||
path: "{{ ansible_env.HOME }}/.kube"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
|
||||
- name: Copy admin.conf to user's .kube directory on controller
|
||||
when: not kube_init_stat.stat.exists
|
||||
fetch:
|
||||
src: /etc/kubernetes/admin.conf
|
||||
dest: "{{ ansible_env.HOME }}/.kube/config"
|
||||
flat: yes
|
||||
|
||||
- name: Install Calico CNI
|
||||
become: no
|
||||
command: kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
when: not kube_init_stat.stat.exists
|
||||
|
||||
- name: Generate join command for control plane nodes
|
||||
when: not kube_init_stat.stat.exists
|
||||
command: kubeadm token create --print-join-command
|
||||
register: control_plane_join_command_raw
|
||||
|
||||
- name: Generate certificate key for control plane join
|
||||
when: not kube_init_stat.stat.exists
|
||||
command: kubeadm init phase upload-certs --upload-certs
|
||||
register: control_plane_cert_key
|
||||
|
||||
- name: Store control plane join command
|
||||
set_fact:
|
||||
control_plane_join_command: "{{ control_plane_join_command_raw.stdout }} --control-plane --certificate-key {{ control_plane_cert_key.stdout_lines[-1] }}"
|
||||
when: not kube_init_stat.stat.exists
|
||||
|
||||
- name: Join other control plane nodes to the cluster
|
||||
when:
|
||||
- inventory_hostname != groups['control_plane'][0]
|
||||
- not kube_init_stat.stat.exists
|
||||
command: "{{ hostvars[groups['control_plane'][0]].control_plane_join_command }}"
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
# Default variables for dns-manager role
|
||||
|
||||
# DNS Configuration
|
||||
dns_management_enabled: true
|
||||
use_hosts_file_fallback: false
|
||||
dns_ttl: 360
|
||||
dns_propagation_wait: 2
|
||||
create_ptr_record: true
|
||||
|
||||
# Cluster Configuration (to be provided by calling role)
|
||||
# cluster_endpoint: "fastpass.local.mk-labs.cloud"
|
||||
# cluster_vip: "10.1.71.53"
|
||||
|
||||
# DNS Server Configuration (from group_vars)
|
||||
# dns_server: "monorail"
|
||||
# base_domain: "local.mk-labs.cloud"
|
||||
# vault_technitium_api_key: "{{ vault_technitium_api_key }}"
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
# role: dns-manager
|
||||
# description: Reusable role for managing DNS entries across clusters using Technitium DNS
|
||||
# author: mk-labs
|
||||
# version: 1.0.0
|
||||
|
||||
- name: Create DNS entry using Technitium DNS task
|
||||
ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/add_technitium_dns_entry.yml"
|
||||
|
||||
- name: Wait for DNS propagation
|
||||
ansible.builtin.pause:
|
||||
seconds: "{{ dns_propagation_wait | default(10) }}"
|
||||
when: dns_management_enabled | default(true)
|
||||
|
||||
- name: Look up A (IPv4) records for example.org
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ query('community.dns.lookup', 'fastpass') }}"
|
||||
@@ -1,8 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/docker-host/handlers/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: restart docker
|
||||
systemd:
|
||||
name: docker
|
||||
state: restarted
|
||||
@@ -1,88 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/docker-host/tasks/main.yml
|
||||
# DESCRIPTION: Prepares an Ubuntu VM to run Docker Compose workloads.
|
||||
# Installs Docker CE, configures the daemon, and ensures
|
||||
# the ansible user (wed) can manage containers.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Install prerequisite packages
|
||||
apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
- lsb-release
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Create keyrings directory
|
||||
file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Add Docker GPG key
|
||||
get_url:
|
||||
url: https://download.docker.com/linux/ubuntu/gpg
|
||||
dest: /etc/apt/keyrings/docker.asc
|
||||
mode: '0644'
|
||||
|
||||
- name: Add Docker repository
|
||||
apt_repository:
|
||||
repo: >-
|
||||
deb [arch={{ ansible_architecture | replace('x86_64', 'amd64') }}
|
||||
signed-by=/etc/apt/keyrings/docker.asc]
|
||||
https://download.docker.com/linux/ubuntu
|
||||
{{ ansible_distribution_release }} stable
|
||||
state: present
|
||||
filename: docker
|
||||
|
||||
- name: Install Docker CE and Compose plugin
|
||||
apt:
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-compose-plugin
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Ensure Docker service is started and enabled
|
||||
systemd:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Add ansible user to docker group
|
||||
user:
|
||||
name: "{{ ansible_user }}"
|
||||
groups: docker
|
||||
append: yes
|
||||
|
||||
- name: Configure Docker daemon
|
||||
copy:
|
||||
dest: /etc/docker/daemon.json
|
||||
content: |
|
||||
{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "10m",
|
||||
"max-file": "3"
|
||||
},
|
||||
"default-address-pools": [
|
||||
{
|
||||
"base": "172.17.0.0/16",
|
||||
"size": 24
|
||||
}
|
||||
]
|
||||
}
|
||||
mode: '0644'
|
||||
notify: restart docker
|
||||
|
||||
- name: Create Docker Compose project directory
|
||||
file:
|
||||
path: /opt/docker
|
||||
state: directory
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: '0775'
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
# Container runtime setup for Kubernetes
|
||||
|
||||
- name: Add containerd repository
|
||||
become: true
|
||||
ansible.builtin.yum_repository:
|
||||
name: docker-ce
|
||||
description: "Docker CE Repository"
|
||||
baseurl: "https://download.docker.com/linux/fedora/{{ ansible_distribution_major_version }}/x86_64/stable"
|
||||
gpgkey: "https://download.docker.com/linux/fedora/gpg"
|
||||
gpgcheck: true
|
||||
enabled: true
|
||||
state: present
|
||||
|
||||
- name: Install containerd
|
||||
become: true
|
||||
ansible.builtin.dnf:
|
||||
name: containerd
|
||||
state: present
|
||||
disable_excludes: docker-ce-stable
|
||||
|
||||
- name: Create containerd config directory
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: /etc/containerd
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Generate default containerd config
|
||||
become: true
|
||||
ansible.builtin.shell: |
|
||||
containerd config default > /etc/containerd/config.toml
|
||||
args:
|
||||
creates: /etc/containerd/config.toml
|
||||
|
||||
- name: Configure containerd for Kubernetes
|
||||
become: true
|
||||
ansible.builtin.replace:
|
||||
path: /etc/containerd/config.toml
|
||||
regexp: 'SystemdCgroup = false'
|
||||
replace: 'SystemdCgroup = true'
|
||||
|
||||
- name: Create containerd service override directory
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/system/containerd.service.d
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure containerd service override
|
||||
become: true
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/containerd.service.d/override.conf
|
||||
content: |
|
||||
[Service]
|
||||
ExecStartPre=-/sbin/modprobe overlay
|
||||
ExecStartPre=-/sbin/modprobe br_netfilter
|
||||
mode: '0644'
|
||||
|
||||
- name: Restart and enable containerd service
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: containerd
|
||||
state: restarted
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
|
||||
- name: Verify containerd is running
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: containerd
|
||||
state: started
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
# FastPass Kubernetes Cluster Preparation for Fedora
|
||||
# This role prepares all nodes for Kubernetes installation
|
||||
|
||||
- name: Include preflight checks
|
||||
ansible.builtin.include_tasks: preflight.yml
|
||||
when: preflight_checks | default(true)
|
||||
|
||||
- name: Include system preparation
|
||||
ansible.builtin.include_tasks: system-prep.yml
|
||||
|
||||
- name: Include container runtime setup
|
||||
ansible.builtin.include_tasks: container-runtime.yml
|
||||
|
||||
- name: Include Kubernetes installation
|
||||
ansible.builtin.include_tasks: kubernetes-install.yml
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
# Preflight checks for Kubernetes installation
|
||||
|
||||
- name: Check if running on supported OS
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_os_family == "RedHat"
|
||||
- ansible_distribution == "Fedora"
|
||||
fail_msg: "This role only supports Fedora"
|
||||
success_msg: "OS check passed"
|
||||
|
||||
- name: Check minimum memory requirement (2GB)
|
||||
ansible.builtin.assert:
|
||||
that: ansible_memtotal_mb >= 2048
|
||||
fail_msg: "Minimum 2GB RAM required, found {{ ansible_memtotal_mb }}MB"
|
||||
success_msg: "Memory check passed: {{ ansible_memtotal_mb }}MB"
|
||||
|
||||
- name: Check minimum CPU cores (2)
|
||||
ansible.builtin.assert:
|
||||
that: ansible_processor_cores >= 2
|
||||
fail_msg: "Minimum 2 CPU cores required, found {{ ansible_processor_cores }}"
|
||||
success_msg: "CPU check passed: {{ ansible_processor_cores }} cores"
|
||||
|
||||
- name: Check available disk space (10GB)
|
||||
ansible.builtin.assert:
|
||||
that: ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first >= 10737418240
|
||||
fail_msg: "Minimum 10GB free space required on root filesystem"
|
||||
success_msg: "Disk space check passed"
|
||||
|
||||
- name: Check if system is 64-bit
|
||||
ansible.builtin.assert:
|
||||
that: ansible_architecture in ['x86_64', 'amd64']
|
||||
fail_msg: "Only 64-bit architectures are supported"
|
||||
success_msg: "Architecture check passed: {{ ansible_architecture }}"
|
||||
|
||||
- name: Check if running as root or with sudo
|
||||
ansible.builtin.assert:
|
||||
that: ansible_become | default(false)
|
||||
fail_msg: "This role requires root privileges (become: true)"
|
||||
success_msg: "Privilege check passed"
|
||||
@@ -1,83 +0,0 @@
|
||||
---
|
||||
# System preparation for Kubernetes
|
||||
|
||||
- name: Disable swap
|
||||
ansible.builtin.command: swapoff -a
|
||||
become: true
|
||||
changed_when: false
|
||||
when: disable_swap | default(true)
|
||||
|
||||
- name: Persist swap off by commenting out swap in fstab
|
||||
become: true
|
||||
ansible.builtin.replace:
|
||||
path: /etc/fstab
|
||||
regexp: '^(\s*)([^#\n]+\s+swap\s+.*)$'
|
||||
replace: '#\2'
|
||||
backup: true
|
||||
when: disable_swap | default(true)
|
||||
|
||||
- name: Load required kernel modules
|
||||
become: true
|
||||
community.general.modprobe:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- overlay
|
||||
- br_netfilter
|
||||
|
||||
- name: Persist kernel modules on boot
|
||||
become: true
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/modules-load.d/k8s.conf
|
||||
content: |
|
||||
overlay
|
||||
br_netfilter
|
||||
mode: '0644'
|
||||
|
||||
- name: Configure required sysctl params for Kubernetes
|
||||
become: true
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/sysctl.d/k8s.conf
|
||||
content: |
|
||||
net.bridge.bridge-nf-call-iptables = 1
|
||||
net.bridge.bridge-nf-call-ip6tables = 1
|
||||
net.ipv4.ip_forward = 1
|
||||
mode: '0644'
|
||||
|
||||
- name: Apply sysctl params without reboot
|
||||
become: true
|
||||
ansible.builtin.command: sysctl --system
|
||||
changed_when: false
|
||||
|
||||
- name: Install required packages
|
||||
become: true
|
||||
ansible.builtin.dnf:
|
||||
name:
|
||||
- python3-libselinux
|
||||
- dnf-plugins-core
|
||||
- curl
|
||||
- wget
|
||||
- ca-certificates
|
||||
state: present
|
||||
|
||||
- name: Set SELinux to permissive mode
|
||||
become: true
|
||||
ansible.posix.selinux:
|
||||
policy: targeted
|
||||
state: "{{ selinux_mode | default('permissive') }}"
|
||||
|
||||
- name: Stop and disable firewalld
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: firewalld
|
||||
state: stopped
|
||||
enabled: false
|
||||
when: not firewall_enabled | default(false)
|
||||
|
||||
- name: Update system packages
|
||||
become: true
|
||||
ansible.builtin.dnf:
|
||||
name: "*"
|
||||
state: latest
|
||||
async: 300
|
||||
poll: 10
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/playbooks/roles/gitea/defaults/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Directory layout on mad-tea-party
|
||||
gitea_compose_dir: /opt/gitea
|
||||
|
||||
# Gitea application settings
|
||||
gitea_domain: gitea.mk-labs.cloud
|
||||
gitea_root_url: "https://{{ gitea_domain }}"
|
||||
gitea_http_port: 3000
|
||||
gitea_ssh_port: 2221
|
||||
|
||||
# Trusted proxy (lightning-lane)
|
||||
gitea_trusted_proxies: 10.1.71.35
|
||||
|
||||
# Timezone
|
||||
gitea_timezone: America/Chicago
|
||||
|
||||
# Database
|
||||
gitea_db_name: gitea
|
||||
gitea_db_user: gitea
|
||||
|
||||
# Traefik dynamic config destination on lightning-lane
|
||||
traefik_dynamic_config_dir: /opt/traefik/dynamic
|
||||
|
||||
# DNS
|
||||
gitea_cname_name: gitea
|
||||
gitea_cname_domain: mk-labs.cloud
|
||||
gitea_cname_target: mad-tea-party.local.mk-labs.cloud
|
||||
|
||||
# OIDC / Authentik SSO
|
||||
gitea_oidc_enabled: true
|
||||
gitea_oidc_client_id: "{{ vault_gitea_oidc_client_id }}"
|
||||
gitea_oidc_client_secret: "{{ vault_gitea_oidc_client_secret }}"
|
||||
gitea_oidc_discovery_url: "https://authentik.local.mk-labs.cloud/application/o/gitea/.well-known/openid-configuration"
|
||||
gitea_oidc_admin_group: "mk-labs-admins"
|
||||
gitea_admin_api_token: "{{ vault_gitea_admin_api_token }}"
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/playbooks/roles/gitea/handlers/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: restart gitea
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ gitea_compose_dir }}"
|
||||
state: present
|
||||
recreate: always
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/playbooks/roles/gitea/tasks/configure_oidc.yml
|
||||
# DESCRIPTION: Registers Authentik as an OAuth2 source in Gitea via the admin
|
||||
# API. Idempotent — skips creation if source already exists.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Check if authentik auth source already exists
|
||||
ansible.builtin.command:
|
||||
cmd: docker exec -u git gitea-gitea-1 gitea admin auth list
|
||||
register: _auth_list
|
||||
changed_when: false
|
||||
|
||||
- name: Create Authentik OAuth2 auth source
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
docker exec -u git gitea-gitea-1 gitea admin auth add-oauth
|
||||
--name authentik
|
||||
--provider openidConnect
|
||||
--key {{ gitea_oidc_client_id }}
|
||||
--secret {{ gitea_oidc_client_secret }}
|
||||
--auto-discover-url {{ gitea_oidc_discovery_url }}
|
||||
--scopes "email profile"
|
||||
--group-claim-name groups
|
||||
--admin-group {{ gitea_oidc_admin_group }}
|
||||
--skip-local-2fa false
|
||||
when: "'authentik' not in _auth_list.stdout"
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/playbooks/roles/gitea/tasks/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# ── Directories ────────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Create Gitea compose directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_compose_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
# ── Compose & env ──────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Deploy Gitea compose file from boilerplate
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/gitea/compose.yaml"
|
||||
dest: "{{ gitea_compose_dir }}/compose.yaml"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: restart gitea
|
||||
|
||||
- name: Deploy .env file with secrets from vault
|
||||
ansible.builtin.template:
|
||||
src: env.j2
|
||||
dest: "{{ gitea_compose_dir }}/.env"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
notify: restart gitea
|
||||
|
||||
# ── Compose stack ──────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Start Gitea stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ gitea_compose_dir }}"
|
||||
state: present
|
||||
recreate: always
|
||||
|
||||
# ── Traefik route ──────────────────────────────────────────────────────────────
|
||||
|
||||
# - name: Deploy Traefik dynamic config for Gitea
|
||||
# ansible.builtin.copy:
|
||||
# src: "{{ playbook_dir }}/../../boilerplates/traefik/dynamic/gitea.yml"
|
||||
# dest: "{{ traefik_dynamic_config_dir }}/gitea.yml"
|
||||
# owner: root
|
||||
# group: root
|
||||
# mode: "0644"
|
||||
# delegate_to: localhost
|
||||
|
||||
# ── DNS ────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# - name: Create DNS CNAME for Gitea
|
||||
# ansible.builtin.include_role:
|
||||
# name: add_service_route
|
||||
# vars:
|
||||
# service_name: "{{ gitea_cname_name }}"
|
||||
# service_domain: "{{ gitea_cname_domain }}"
|
||||
# target_host: "{{ gitea_cname_target }}"
|
||||
- name: Configure Authentik OIDC source
|
||||
ansible.builtin.import_tasks: configure_oidc.yml
|
||||
when: gitea_oidc_enabled | bool
|
||||
tags: oidc
|
||||
@@ -1,12 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/playbooks/roles/gitea/templates/env.j2
|
||||
# DESCRIPTION: Environment file for Gitea Docker Compose stack.
|
||||
# Populated from Ansible Vault at deploy time.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
DATABASE_PASSWORD={{ gitea_db_password }}
|
||||
|
||||
# OIDC auto-registration (Gitea reads these at runtime)
|
||||
GITEA__oauth2__ENABLE_AUTO_REGISTRATION=true
|
||||
GITEA__oauth2__UPDATE_AVATAR=true
|
||||
GITEA__oauth2__ACCOUNT_LINKING=login
|
||||
@@ -1,147 +0,0 @@
|
||||
---
|
||||
# ansible/roles/haproxy/defaults/main.yml
|
||||
|
||||
# HAProxy version and installation
|
||||
haproxy_version: "3.2"
|
||||
haproxy_install_method: "package" # package or source
|
||||
|
||||
# System configuration
|
||||
haproxy_user: haproxy
|
||||
haproxy_group: haproxy
|
||||
haproxy_chroot_dir: /var/lib/haproxy
|
||||
haproxy_config_dir: /etc/haproxy
|
||||
haproxy_log_dir: /var/log/haproxy
|
||||
|
||||
# Global settings
|
||||
haproxy_global_maxconn: 4096
|
||||
haproxy_global_log_facility: local0
|
||||
haproxy_global_log_level: info
|
||||
haproxy_global_nbthread: "{{ ansible_processor_vcpus }}"
|
||||
|
||||
# Default timeouts (milliseconds)
|
||||
haproxy_timeout_connect: 5000
|
||||
haproxy_timeout_client: 50000
|
||||
haproxy_timeout_server: 50000
|
||||
|
||||
# Stats interface
|
||||
haproxy_stats_enable: true
|
||||
haproxy_stats_port: 8404
|
||||
haproxy_stats_uri: /stats
|
||||
haproxy_stats_refresh: 30s
|
||||
haproxy_stats_username: admin
|
||||
haproxy_stats_password: "{{ vault_haproxy_stats_password | default('changeme') }}"
|
||||
|
||||
# SSL/TLS configuration
|
||||
haproxy_ssl_cert_dir: "{{ haproxy_config_dir }}/certs"
|
||||
haproxy_ssl_default_crt: "{{ haproxy_ssl_cert_dir }}/default.pem"
|
||||
haproxy_ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
|
||||
haproxy_ssl_options: "ssl-min-ver TLSv1.2 no-tls-tickets"
|
||||
|
||||
# Health check defaults
|
||||
haproxy_health_check_interval: 2000
|
||||
haproxy_health_check_timeout: 1000
|
||||
haproxy_health_check_rise: 2
|
||||
haproxy_health_check_fall: 3
|
||||
|
||||
# Service discovery integration
|
||||
haproxy_netbox_integration: false # true
|
||||
haproxy_netbox_url: "http://fire-station.local.mk-labs.cloud:8000"
|
||||
haproxy_netbox_token: "{{ vault_netbox_token | default('') }}"
|
||||
|
||||
# Frontends configuration
|
||||
haproxy_frontends: []
|
||||
# - name: http_front
|
||||
# bind: "*:80"
|
||||
# mode: http
|
||||
# redirect: "scheme https code 301"
|
||||
#
|
||||
# - name: https_front
|
||||
# bind: "*:443 ssl crt {{ haproxy_ssl_cert_dir }}"
|
||||
# mode: http
|
||||
# options:
|
||||
# - "http-server-close"
|
||||
# - "forwardfor"
|
||||
# acls:
|
||||
# - "netbox hdr(host) -i fire-station.local.mk-labs.cloud"
|
||||
# - "wordpress hdr(host) -i be-our-guest.local.mk-labs.cloud"
|
||||
# use_backends:
|
||||
# - "netbox_back if netbox"
|
||||
# - "wordpress_back if wordpress"
|
||||
|
||||
# Backends configuration
|
||||
haproxy_backends: []
|
||||
# - name: netbox_back
|
||||
# mode: http
|
||||
# balance: roundrobin
|
||||
# options:
|
||||
# - "httpchk GET /"
|
||||
# servers:
|
||||
# - name: fire-station
|
||||
# address: 10.1.71.102:8000
|
||||
# check: true
|
||||
|
||||
# TCP services configuration
|
||||
haproxy_tcp_services: []
|
||||
# - name: postgres
|
||||
# frontend_port: 5432
|
||||
# backend_port: 5432
|
||||
# balance: leastconn
|
||||
# servers:
|
||||
# - name: netbox-db
|
||||
# address: 10.1.71.102
|
||||
|
||||
# Firewall configuration
|
||||
haproxy_configure_firewall: true
|
||||
haproxy_firewall_allowed_ports:
|
||||
- 80/tcp
|
||||
- 443/tcp
|
||||
- 8404/tcp
|
||||
|
||||
# Monitoring and observability
|
||||
haproxy_prometheus_exporter: false
|
||||
haproxy_prometheus_port: 9101
|
||||
|
||||
# Logging
|
||||
haproxy_rsyslog_config: true
|
||||
haproxy_log_rotation: true
|
||||
|
||||
# Certificate management - Certbot with Cloudflare DNS
|
||||
haproxy_certbot_enable: true
|
||||
haproxy_certbot_challenge_method: "dns-cloudflare" # or "webroot" for HTTP-01
|
||||
haproxy_certbot_email: "ryan.blundon@protonmail.com"
|
||||
haproxy_certbot_staging: false # Set to true for testing with Let's Encrypt staging
|
||||
|
||||
# Cloudflare DNS configuration
|
||||
haproxy_certbot_cloudflare_api_token: "{{ vault_cloudflare_api_token }}"
|
||||
haproxy_certbot_cloudflare_credentials_file: "/etc/letsencrypt/cloudflare.ini"
|
||||
|
||||
# Certificate domains - DNS-01 supports wildcards
|
||||
haproxy_certbot_domains:
|
||||
- domain: "*.mk-labs.cloud"
|
||||
include_base: true
|
||||
- domain: "*.local.mk-labs.cloud"
|
||||
include_base: true # Also include local.mk-labs.cloud
|
||||
- domain: "lightning-lane.local.mk-labs.cloud"
|
||||
include_base: false
|
||||
|
||||
# Alternative: specific domains without wildcard
|
||||
# haproxy_certbot_domains:
|
||||
# - domain: "lightning-lane.local.mk-labs.cloud"
|
||||
# - domain: "fire-station.local.mk-labs.cloud"
|
||||
# - domain: "be-our-guest.local.mk-labs.cloud"
|
||||
|
||||
# Certbot renewal settings
|
||||
haproxy_certbot_renewal_cron_minute: "0"
|
||||
haproxy_certbot_renewal_cron_hour: "3" # 3 AM daily
|
||||
haproxy_certbot_renewal_cron_day: "*"
|
||||
haproxy_certbot_post_hook: "{{ haproxy_config_dir }}/certbot-post-hook.sh"
|
||||
|
||||
# Certificate storage
|
||||
haproxy_certbot_cert_dir: "/etc/letsencrypt/live"
|
||||
haproxy_certbot_archive_dir: "/etc/letsencrypt/archive"
|
||||
|
||||
# DNS propagation wait time (seconds)
|
||||
haproxy_certbot_dns_propagation_seconds: 60
|
||||
|
||||
# Fallback to self-signed if Certbot fails
|
||||
haproxy_certbot_fallback_selfsigned: true
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
# ansible/roles/haproxy/handlers/main.yml
|
||||
|
||||
- name: Reload haproxy
|
||||
ansible.builtin.systemd:
|
||||
name: haproxy
|
||||
state: reloaded
|
||||
listen: reload haproxy
|
||||
|
||||
- name: Restart haproxy
|
||||
ansible.builtin.systemd:
|
||||
name: haproxy
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
listen: restart haproxy
|
||||
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
listen: reload systemd
|
||||
|
||||
- name: Restart rsyslog
|
||||
ansible.builtin.systemd:
|
||||
name: rsyslog
|
||||
state: restarted
|
||||
listen: restart rsyslog
|
||||
@@ -1,202 +0,0 @@
|
||||
---
|
||||
# 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 }}"
|
||||
@@ -1,78 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
@@ -1,94 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
@@ -1,128 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
# 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]
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
# 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' }}"
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
#!/bin/bash
|
||||
# {{ ansible_managed }}
|
||||
# Certbot post-renewal hook for HAProxy
|
||||
# Combines certificate and private key for HAProxy consumption
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
LETSENCRYPT_LIVE="/etc/letsencrypt/live"
|
||||
HAPROXY_CERT_DIR="{{ haproxy_ssl_cert_dir }}/live"
|
||||
HAPROXY_USER="{{ haproxy_user }}"
|
||||
HAPROXY_GROUP="{{ haproxy_group }}"
|
||||
LOG_FILE="{{ haproxy_log_dir }}/certbot-hook.log"
|
||||
|
||||
# Ensure HAProxy cert directory exists
|
||||
mkdir -p "$HAPROXY_CERT_DIR"
|
||||
|
||||
# Logging function
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log "Starting certificate deployment for HAProxy"
|
||||
|
||||
# Track if any certificates were updated
|
||||
CERTS_UPDATED=false
|
||||
|
||||
# Process each certificate in Let's Encrypt live directory
|
||||
for cert_dir in "$LETSENCRYPT_LIVE"/*; do
|
||||
if [ ! -d "$cert_dir" ] || [ "$(basename "$cert_dir")" = "README" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
CERT_NAME=$(basename "$cert_dir")
|
||||
|
||||
# Check if certificate files exist
|
||||
if [ ! -f "$cert_dir/fullchain.pem" ] || [ ! -f "$cert_dir/privkey.pem" ]; then
|
||||
log "WARNING: Missing certificate files for $CERT_NAME, skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
log "Processing certificate: $CERT_NAME"
|
||||
|
||||
# Create combined PEM file (fullchain + privkey)
|
||||
# HAProxy requires the certificate chain and private key in a single file
|
||||
COMBINED_PEM="$HAPROXY_CERT_DIR/${CERT_NAME}.pem"
|
||||
|
||||
# Combine certificates
|
||||
cat "$cert_dir/fullchain.pem" "$cert_dir/privkey.pem" > "$COMBINED_PEM.tmp"
|
||||
|
||||
# Check if certificate actually changed
|
||||
if [ -f "$COMBINED_PEM" ] && cmp -s "$COMBINED_PEM" "$COMBINED_PEM.tmp"; then
|
||||
log "Certificate $CERT_NAME unchanged, skipping"
|
||||
rm "$COMBINED_PEM.tmp"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Move new certificate into place
|
||||
mv "$COMBINED_PEM.tmp" "$COMBINED_PEM"
|
||||
|
||||
# Set proper permissions
|
||||
chown "$HAPROXY_USER:$HAPROXY_GROUP" "$COMBINED_PEM"
|
||||
chmod 600 "$COMBINED_PEM"
|
||||
|
||||
log "Created/Updated: $COMBINED_PEM"
|
||||
CERTS_UPDATED=true
|
||||
done
|
||||
|
||||
# Create/update default certificate symlink
|
||||
# Use the first wildcard cert or the first available cert as default
|
||||
DEFAULT_CERT="{{ haproxy_ssl_default_crt }}"
|
||||
WILDCARD_CERT="$HAPROXY_CERT_DIR/local.mk-labs.cloud.pem"
|
||||
|
||||
if [ -f "$WILDCARD_CERT" ]; then
|
||||
ln -sf "$WILDCARD_CERT" "$DEFAULT_CERT"
|
||||
log "Updated default certificate symlink to wildcard cert"
|
||||
elif [ -n "$(ls -A "$HAPROXY_CERT_DIR" 2>/dev/null)" ]; then
|
||||
FIRST_CERT=$(ls "$HAPROXY_CERT_DIR"/*.pem 2>/dev/null | head -n1)
|
||||
ln -sf "$FIRST_CERT" "$DEFAULT_CERT"
|
||||
log "Updated default certificate symlink to $FIRST_CERT"
|
||||
fi
|
||||
|
||||
# Only reload HAProxy if certificates were actually updated
|
||||
if [ "$CERTS_UPDATED" = true ]; then
|
||||
log "Certificates updated, validating HAProxy configuration"
|
||||
|
||||
# Validate HAProxy configuration
|
||||
if haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg >/dev/null 2>&1; then
|
||||
log "HAProxy configuration valid, reloading service"
|
||||
|
||||
if systemctl reload haproxy; then
|
||||
log "HAProxy reloaded successfully"
|
||||
else
|
||||
log "ERROR: Failed to reload HAProxy"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log "ERROR: HAProxy configuration validation failed"
|
||||
haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg 2>&1 | tee -a "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log "No certificates were updated, skipping HAProxy reload"
|
||||
fi
|
||||
|
||||
log "Certificate deployment complete"
|
||||
exit 0
|
||||
@@ -1,12 +0,0 @@
|
||||
# {{ ansible_managed }}
|
||||
# Cloudflare API credentials for Certbot DNS-01 challenge
|
||||
# This file contains sensitive information and should be protected
|
||||
|
||||
# Cloudflare API Token (RECOMMENDED)
|
||||
# Create at: https://dash.cloudflare.com/profile/api-tokens
|
||||
# Required permissions: Zone:DNS:Edit for specific zone
|
||||
dns_cloudflare_api_token = {{ haproxy_certbot_cloudflare_api_token }}
|
||||
|
||||
# Alternative: Global API Key (NOT RECOMMENDED - too permissive)
|
||||
# dns_cloudflare_email = your-email@example.com
|
||||
# dns_cloudflare_api_key = your-global-api-key
|
||||
@@ -1,188 +0,0 @@
|
||||
{# ansible/roles/haproxy/templates/haproxy.cfg.j2 #}
|
||||
#---------------------------------------------------------------------
|
||||
# HAProxy Configuration for {{ inventory_hostname }}
|
||||
# Generated by Ansible - DO NOT EDIT MANUALLY
|
||||
# Magic Kingdom Labs - lightning-lane load balancer
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# Global settings
|
||||
#---------------------------------------------------------------------
|
||||
global
|
||||
log 127.0.0.1 {{ haproxy_global_log_facility }} {{ haproxy_global_log_level }}
|
||||
chroot {{ haproxy_chroot_dir }}
|
||||
pidfile /var/run/haproxy.pid
|
||||
maxconn {{ haproxy_global_maxconn }}
|
||||
user {{ haproxy_user }}
|
||||
group {{ haproxy_group }}
|
||||
daemon
|
||||
nbthread {{ haproxy_global_nbthread }}
|
||||
|
||||
# Runtime socket for management
|
||||
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
|
||||
stats timeout 30s
|
||||
|
||||
# SSL/TLS configuration
|
||||
ssl-default-bind-ciphers {{ haproxy_ssl_ciphers }}
|
||||
ssl-default-bind-options {{ haproxy_ssl_options }}
|
||||
ssl-default-server-ciphers {{ haproxy_ssl_ciphers }}
|
||||
ssl-default-server-options {{ haproxy_ssl_options }}
|
||||
|
||||
# Performance tuning
|
||||
tune.ssl.default-dh-param 2048
|
||||
tune.ssl.cachesize 100000
|
||||
tune.ssl.lifetime 600
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# Common defaults
|
||||
#---------------------------------------------------------------------
|
||||
defaults
|
||||
mode http
|
||||
log global
|
||||
option httplog
|
||||
option dontlognull
|
||||
option http-server-close
|
||||
option redispatch
|
||||
retries 3
|
||||
timeout connect {{ haproxy_timeout_connect }}
|
||||
timeout client {{ haproxy_timeout_client }}
|
||||
timeout server {{ haproxy_timeout_server }}
|
||||
timeout http-request 10s
|
||||
timeout http-keep-alive 10s
|
||||
timeout check 10s
|
||||
maxconn 3000
|
||||
|
||||
# Error pages
|
||||
errorfile 400 /etc/haproxy/errors/400.http
|
||||
errorfile 403 /etc/haproxy/errors/403.http
|
||||
errorfile 408 /etc/haproxy/errors/408.http
|
||||
errorfile 500 /etc/haproxy/errors/500.http
|
||||
errorfile 502 /etc/haproxy/errors/502.http
|
||||
errorfile 503 /etc/haproxy/errors/503.http
|
||||
errorfile 504 /etc/haproxy/errors/504.http
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# Stats interface
|
||||
#---------------------------------------------------------------------
|
||||
{% if haproxy_stats_enable %}
|
||||
listen stats
|
||||
bind *:{{ haproxy_stats_port }}
|
||||
mode http
|
||||
stats enable
|
||||
stats uri {{ haproxy_stats_uri }}
|
||||
stats refresh {{ haproxy_stats_refresh }}
|
||||
stats realm Magic\ Kingdom\ Labs\ -\ HAProxy\ Statistics
|
||||
stats auth {{ haproxy_stats_username }}:{{ haproxy_stats_password }}
|
||||
stats admin if TRUE
|
||||
{% endif %}
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# HTTP Redirect Frontend (HTTP -> HTTPS)
|
||||
#---------------------------------------------------------------------
|
||||
frontend http_front
|
||||
bind *:80
|
||||
mode http
|
||||
|
||||
# Redirect all HTTP traffic to HTTPS
|
||||
redirect scheme https code 301
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# HTTPS Frontend with SNI
|
||||
#---------------------------------------------------------------------
|
||||
frontend https_front
|
||||
bind *:443 ssl crt {{ haproxy_ssl_cert_dir }}/live crt {{ haproxy_ssl_default_crt }} alpn h2,http/1.1
|
||||
mode http
|
||||
|
||||
# Security headers
|
||||
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
http-response set-header X-Frame-Options "SAMEORIGIN"
|
||||
http-response set-header X-Content-Type-Options "nosniff"
|
||||
http-response set-header X-XSS-Protection "1; mode=block"
|
||||
|
||||
# Logging
|
||||
option httplog
|
||||
option forwardfor
|
||||
|
||||
{% if haproxy_frontends is defined and haproxy_frontends | length > 0 %}
|
||||
{% for frontend in haproxy_frontends %}
|
||||
{% if frontend.acls is defined %}
|
||||
# ACLs for {{ frontend.name }}
|
||||
{% for acl in frontend.acls %}
|
||||
acl {{ acl }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for frontend in haproxy_frontends %}
|
||||
{% if frontend.use_backends is defined %}
|
||||
# Backend routing for {{ frontend.name }}
|
||||
{% for use_backend in frontend.use_backends %}
|
||||
use_backend {{ use_backend }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
# Default backend
|
||||
default_backend no_match_back
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# Backends
|
||||
#---------------------------------------------------------------------
|
||||
{% for backend in haproxy_backends %}
|
||||
backend {{ backend.name }}
|
||||
mode {{ backend.mode | default('http') }}
|
||||
balance {{ backend.balance | default('roundrobin') }}
|
||||
{% if backend.options is defined %}
|
||||
{% for option in backend.options %}
|
||||
option {{ option }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if backend.http_check is defined %}
|
||||
option httpchk {{ backend.http_check }}
|
||||
{% endif %}
|
||||
{% if backend.servers is defined %}
|
||||
{% for server in backend.servers %}
|
||||
server {{ server.name }} {{ server.address }}{% if server.check | default(false) %} check{% endif %}{% if server.backup | default(false) %} backup{% endif %}{% if server.weight is defined %} weight {{ server.weight }}{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
# Default backend for unmatched requests
|
||||
backend no_match_back
|
||||
mode http
|
||||
http-request deny deny_status 404
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# TCP Services
|
||||
#---------------------------------------------------------------------
|
||||
{% for tcp_service in haproxy_tcp_services %}
|
||||
# {{ tcp_service.name | upper }} Service (TCP)
|
||||
frontend {{ tcp_service.name }}_front
|
||||
bind *:{{ tcp_service.frontend_port }}
|
||||
mode tcp
|
||||
option tcplog
|
||||
{% if tcp_service.timeout_client is defined %}
|
||||
timeout client {{ tcp_service.timeout_client }}
|
||||
{% endif %}
|
||||
default_backend {{ tcp_service.name }}_back
|
||||
|
||||
backend {{ tcp_service.name }}_back
|
||||
mode tcp
|
||||
balance {{ tcp_service.balance | default('roundrobin') }}
|
||||
{% if tcp_service.timeout_server is defined %}
|
||||
timeout server {{ tcp_service.timeout_server }}
|
||||
{% endif %}
|
||||
{% if tcp_service.timeout_connect is defined %}
|
||||
timeout connect {{ tcp_service.timeout_connect }}
|
||||
{% endif %}
|
||||
{% if tcp_service.servers is defined %}
|
||||
{% for server in tcp_service.servers %}
|
||||
server {{ server.name }} {{ server.address }}:{{ tcp_service.backend_port }}{% if server.check | default(true) %} check{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
@@ -1,14 +0,0 @@
|
||||
{# ansible/roles/haproxy/templates/logrotate.j2 #}
|
||||
{{ haproxy_log_dir }}/*.log {
|
||||
daily
|
||||
rotate 14
|
||||
missingok
|
||||
notifempty
|
||||
compress
|
||||
delaycompress
|
||||
sharedscripts
|
||||
postrotate
|
||||
/bin/kill -HUP `cat /var/run/rsyslog.pid 2> /dev/null` 2> /dev/null || true
|
||||
/bin/kill -HUP `cat /var/run/haproxy.pid 2> /dev/null` 2> /dev/null || true
|
||||
endscript
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
# Hermes service user
|
||||
hermes_user: hermes
|
||||
hermes_group: hermes
|
||||
hermes_home: /home/hermes
|
||||
|
||||
# Install flags
|
||||
# Set to true if you don't need browser automation (skips Playwright/Chromium)
|
||||
hermes_skip_browser: false
|
||||
|
||||
# systemd service name
|
||||
hermes_service_name: hermes
|
||||
|
||||
# Path where hermes binary will be accessible system-wide
|
||||
hermes_bin_symlink: /usr/local/bin/hermes
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
- name: reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: restart hermes
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ hermes_service_name }}"
|
||||
state: restarted
|
||||
@@ -1,156 +0,0 @@
|
||||
---
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. System prerequisites (run as root via become)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Install system packages required by Hermes installer
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- git
|
||||
- curl
|
||||
- ffmpeg
|
||||
- ripgrep
|
||||
state: present
|
||||
update_cache: true
|
||||
become: true
|
||||
|
||||
- name: Install Node.js 22 (required for browser automation and WhatsApp bridge)
|
||||
block:
|
||||
- name: Download NodeSource setup script
|
||||
ansible.builtin.get_url:
|
||||
url: https://deb.nodesource.com/setup_22.x
|
||||
dest: /tmp/nodesource_setup.sh
|
||||
mode: "0755"
|
||||
|
||||
- name: Run NodeSource setup script
|
||||
ansible.builtin.command: bash /tmp/nodesource_setup.sh
|
||||
args:
|
||||
creates: /etc/apt/sources.list.d/nodesource.list
|
||||
|
||||
- name: Install nodejs
|
||||
ansible.builtin.apt:
|
||||
name: nodejs
|
||||
state: present
|
||||
update_cache: true
|
||||
become: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Install Playwright system deps for Chromium (root-only step)
|
||||
# Per docs: this is the one thing that genuinely needs root.
|
||||
# Skipped entirely if hermes_skip_browser is true.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Install Playwright Chromium system dependencies
|
||||
ansible.builtin.command: npx --yes playwright install-deps chromium
|
||||
become: true
|
||||
when: not hermes_skip_browser
|
||||
changed_when: true
|
||||
environment:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Create dedicated hermes service user
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Create hermes group
|
||||
ansible.builtin.group:
|
||||
name: "{{ hermes_group }}"
|
||||
state: present
|
||||
system: true
|
||||
become: true
|
||||
|
||||
- name: Create hermes user
|
||||
ansible.builtin.user:
|
||||
name: "{{ hermes_user }}"
|
||||
group: "{{ hermes_group }}"
|
||||
home: "{{ hermes_home }}"
|
||||
shell: /bin/bash
|
||||
system: true
|
||||
create_home: true
|
||||
comment: "Hermes Agent service account"
|
||||
become: true
|
||||
|
||||
- name: Grant hermes user passwordless sudo
|
||||
ansible.builtin.copy:
|
||||
content: "hermes ALL=(ALL) NOPASSWD:ALL\n"
|
||||
dest: /etc/sudoers.d/hermes
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
become: true
|
||||
|
||||
- name: Ensure hermes home directory has correct permissions
|
||||
ansible.builtin.file:
|
||||
path: "{{ hermes_home }}"
|
||||
owner: "{{ hermes_user }}"
|
||||
group: "{{ hermes_group }}"
|
||||
mode: "0750"
|
||||
state: directory
|
||||
become: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Run the Hermes installer as the hermes user
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Check if hermes is already installed
|
||||
ansible.builtin.stat:
|
||||
path: "{{ hermes_home }}/.hermes/hermes-agent/venv/bin/hermes"
|
||||
register: hermes_binary
|
||||
|
||||
- name: Run Hermes installer as hermes user
|
||||
ansible.builtin.shell: |
|
||||
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh \
|
||||
| bash -s -- --skip-setup{% if hermes_skip_browser %} --skip-browser{% endif %}
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: "{{ hermes_user }}"
|
||||
environment:
|
||||
HOME: "{{ hermes_home }}"
|
||||
PATH: "{{ hermes_home }}/.local/bin:/usr/local/bin:/usr/bin:/bin"
|
||||
when: not hermes_binary.stat.exists
|
||||
changed_when: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Symlink hermes binary into system PATH
|
||||
# Per docs: service accounts often lack ~/.local/bin in PATH; symlink
|
||||
# into /usr/local/bin so hermes is always accessible.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Symlink hermes binary to system PATH
|
||||
ansible.builtin.file:
|
||||
src: "{{ hermes_home }}/.hermes/hermes-agent/venv/bin/hermes"
|
||||
dest: "{{ hermes_bin_symlink }}"
|
||||
state: link
|
||||
force: true
|
||||
become: true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Install systemd service unit
|
||||
# NOTE: The service starts in 'gateway' mode for persistent operation.
|
||||
# You must run 'sudo -u hermes hermes setup' interactively on first boot
|
||||
# to configure your LLM provider and any messaging gateways before
|
||||
# enabling the service.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
- name: Deploy hermes systemd service unit
|
||||
ansible.builtin.template:
|
||||
src: hermes.service.j2
|
||||
dest: /etc/systemd/system/{{ hermes_service_name }}.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
become: true
|
||||
notify:
|
||||
- reload systemd
|
||||
|
||||
- name: Flush handlers to reload systemd now
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: Enable hermes service (but do not start — config required first)
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ hermes_service_name }}"
|
||||
enabled: true
|
||||
state: stopped
|
||||
become: true
|
||||
@@ -1,23 +0,0 @@
|
||||
[Unit]
|
||||
Description=Hermes Agent (Nous Research)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ hermes_user }}
|
||||
Group={{ hermes_group }}
|
||||
WorkingDirectory={{ hermes_home }}
|
||||
Environment="HOME={{ hermes_home }}"
|
||||
Environment="PATH={{ hermes_home }}/.local/bin:/usr/local/bin:/usr/bin:/bin"
|
||||
Environment="HERMES_HOME={{ hermes_home }}/.hermes"
|
||||
ExecStart={{ hermes_home }}/.hermes/hermes-agent/venv/bin/hermes gateway
|
||||
TimeoutStopSec=200
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=hermes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,95 +0,0 @@
|
||||
---
|
||||
# Role to install and configure a n8n server
|
||||
|
||||
- name: 1. Pre-install system updates
|
||||
become: true
|
||||
ansible.builtin.apt:
|
||||
name: '*'
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: 2. Install required packages
|
||||
become: true
|
||||
ansible.builtin.package:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- curl
|
||||
- wget
|
||||
- gnupg
|
||||
- lsb-release
|
||||
- ca-certificates
|
||||
- software-properties-common
|
||||
|
||||
- name: 3. Enable UFW
|
||||
become: true
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
|
||||
- name: 4. Open firewall ports for n8n
|
||||
become: true
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ item }}"
|
||||
proto: tcp
|
||||
loop:
|
||||
- 22
|
||||
- 5678
|
||||
|
||||
- name: 5. Download node installer
|
||||
# become: true
|
||||
ansible.builtin.get_url:
|
||||
url: 'https://deb.nodesource.com/setup_lts.x'
|
||||
dest: '/tmp/setup_lts.x'
|
||||
mode: '0755'
|
||||
|
||||
- name: 6. Run node installer
|
||||
become: true
|
||||
ansible.builtin.shell:
|
||||
cmd: '/tmp/setup_lts.x'
|
||||
|
||||
- name: 7. Install Node.js and npm
|
||||
become: true
|
||||
ansible.builtin.package:
|
||||
name: nodejs
|
||||
state: present
|
||||
|
||||
- name: 8. Install npm@latest
|
||||
become: true
|
||||
ansible.builtin.shell:
|
||||
cmd: 'sudo npm install -g npm@latest'
|
||||
|
||||
- name: 9. Install "n8n" node.js package globally.
|
||||
become: true
|
||||
community.general.npm:
|
||||
name: n8n
|
||||
global: true
|
||||
|
||||
- name: 10. Copy n8n.service to /etc/systemd/system
|
||||
become: true
|
||||
ansible.builtin.template:
|
||||
dest: /etc/systemd/system/n8n.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
src: n8n.service.j2
|
||||
|
||||
- name: 11. Create n8n user
|
||||
become: true
|
||||
ansible.builtin.user:
|
||||
name: n8n
|
||||
state: present
|
||||
shell: /bin/bash
|
||||
home: /home/n8n
|
||||
createhome: true
|
||||
|
||||
- name: 12. Reload systemd to re-read configs
|
||||
ansible.builtin.systemd_service:
|
||||
daemon_reload: true
|
||||
|
||||
- name: 13. Start n8n service
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: n8n.service
|
||||
state: restarted
|
||||
enabled: true
|
||||
@@ -1,22 +0,0 @@
|
||||
[Unit]
|
||||
Description=n8n - Workflow Automation Tool
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=n8n
|
||||
Group=n8n
|
||||
ExecStart=/usr/local/bin/n8n
|
||||
WorkingDirectory=/home/n8n
|
||||
Environment=NODE_ENV=production
|
||||
Environment=N8N_BASIC_AUTH_ACTIVE=true
|
||||
Environment=N8N_BASIC_AUTH_USER=admin
|
||||
#Environment=N8N_BASIC_AUTH_PASSWORD=your_secure_password
|
||||
Environment=N8N_HOST=0.0.0.0
|
||||
Environment=N8N_PORT=5678
|
||||
Environment=N8N_PROTOCOL=http
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
# Role to disable swap
|
||||
|
||||
- name: 1. Check if swap is enabled
|
||||
ansible.builtin.shell: swapon --show=NAME --noheadings
|
||||
register: swap_status
|
||||
changed_when: false
|
||||
|
||||
- name: 2. Disable swap
|
||||
become: true
|
||||
when: swap_status.stdout != ''
|
||||
block:
|
||||
- name: 2.1 Disable swap
|
||||
ansible.builtin.shell: swapoff -a
|
||||
|
||||
- name: 2.2 Persist swap off by commenting out swap in fstab
|
||||
ansible.builtin.replace:
|
||||
path: /etc/fstab
|
||||
regexp: '^([^#].*?\sswap\s+sw\s+.*)$'
|
||||
replace: '# \1'
|
||||
backup: true
|
||||
|
||||
- name: 2.3 Remove swap file
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ swap_status.stdout }}"
|
||||
state: absent
|
||||
|
||||
- name: 2.4 Remove zram-generator-defaults
|
||||
become: true
|
||||
when: ansible_os_family == "RedHat"
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/zram-generator.conf
|
||||
state: absent
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
# Role to install and configure an Chrony NTP server
|
||||
|
||||
- name: 1. Install Chrony NTP for time synchronization
|
||||
become: true
|
||||
ansible.builtin.dnf:
|
||||
name: chrony
|
||||
state: present
|
||||
|
||||
- name: 2.1 Remove DHCP NTP servers from config
|
||||
become: true
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/chrony.conf
|
||||
line: "server unifi.int.mk-labs.cloud iburst"
|
||||
state: absent
|
||||
|
||||
- name: 2.2 Ensure specific NTP servers are present
|
||||
become: true
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/chrony.conf
|
||||
line: "server {{ item }} iburst"
|
||||
state: present
|
||||
loop: "{{ ntp_servers }}"
|
||||
|
||||
- name: 3. Ensure network allowed are present
|
||||
become: true
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/chrony.conf
|
||||
line: "allow {{ item }}"
|
||||
state: present
|
||||
loop: "{{ allowed_networks }}"
|
||||
|
||||
- name: 4.Restart ntpd service
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: chronyd
|
||||
state: restarted
|
||||
enabled: true
|
||||
|
||||
- name: 5. Allow NTP traffic
|
||||
become: true
|
||||
ansible.posix.firewalld:
|
||||
service: ntp
|
||||
permanent: true
|
||||
immediate: true
|
||||
state: enabled
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
prometheus_version: v2.40.1
|
||||
grafana_version: "9.2.5"
|
||||
alertmanager_version: v0.24.0
|
||||
#alertmanager_smtp_password: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
64306663363562356132323065396635636630373031303739323666373262663961393132316333
|
||||
6135653763363566303331313639633030623530646239310a353236343035643132646230333466
|
||||
36336439376131333630346563323833313164353265313264643232373465633561663331396133
|
||||
3163303166373166390a396131303239356139653063616437363933333130393563646338663933
|
||||
3966
|
||||
@@ -1,8 +0,0 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: Pre-loaded local dashboards
|
||||
type: file
|
||||
options:
|
||||
foldersFromFilesStructure: true
|
||||
path: /var/lib/grafana/dashboards
|
||||
@@ -1,814 +0,0 @@
|
||||
{
|
||||
"__inputs": [
|
||||
{
|
||||
"name": "DS_PROMETHEUS",
|
||||
"label": "prometheus",
|
||||
"description": "Prometheus as the datasource is obligatory",
|
||||
"type": "datasource",
|
||||
"pluginId": "prometheus",
|
||||
"pluginName": "Prometheus"
|
||||
}
|
||||
],
|
||||
"__requires": [
|
||||
{
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "7.4.5"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "graph",
|
||||
"name": "Graph",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "datasource",
|
||||
"id": "prometheus",
|
||||
"name": "Prometheus",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "table",
|
||||
"name": "Table",
|
||||
"version": ""
|
||||
}
|
||||
],
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": 14282,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"iteration": 1617715580880,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 8,
|
||||
"panels": [],
|
||||
"title": "CPU",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 15,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.4.5",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(container_cpu_usage_seconds_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name) *100",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "CPU Usage",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:606",
|
||||
"format": "percent",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:607",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
},
|
||||
"id": 11,
|
||||
"panels": [],
|
||||
"title": "Memory",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 9,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.4.5",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(container_memory_rss{instance=~\"$host\",name=~\"$container\",name=~\".+\"}) by (name)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Memory Usage",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:606",
|
||||
"format": "bytes",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:607",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 9
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 14,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.4.5",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(container_memory_cache{instance=~\"$host\",name=~\"$container\",name=~\".+\"}) by (name)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Memory Cached",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:606",
|
||||
"format": "bytes",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:607",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 17
|
||||
},
|
||||
"id": 2,
|
||||
"panels": [],
|
||||
"title": "Network",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 18
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 4,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"hideEmpty": false,
|
||||
"hideZero": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"sideWidth": null,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.4.5",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(container_network_receive_bytes_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Received Network Traffic",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:674",
|
||||
"format": "Bps",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:675",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 18
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 6,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "7.4.5",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(container_network_transmit_bytes_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name)",
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Sent Network Traffic",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:832",
|
||||
"format": "Bps",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:833",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "Prometheus",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 26
|
||||
},
|
||||
"id": 19,
|
||||
"panels": [],
|
||||
"title": "Misc",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"align": null,
|
||||
"filterable": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "id"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.width",
|
||||
"value": 260
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Running"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "d"
|
||||
},
|
||||
{
|
||||
"id": "decimals",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.displayMode",
|
||||
"value": "color-text"
|
||||
},
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"fixedColor": "dark-green",
|
||||
"mode": "fixed"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 27
|
||||
},
|
||||
"id": 17,
|
||||
"options": {
|
||||
"showHeader": true,
|
||||
"sortBy": []
|
||||
},
|
||||
"pluginVersion": "7.4.5",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "(time() - container_start_time_seconds{instance=~\"$host\",name=~\"$container\",name=~\".+\"})/86400",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Containers Info",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "filterFieldsByName",
|
||||
"options": {
|
||||
"include": {
|
||||
"names": [
|
||||
"container_label_com_docker_compose_project",
|
||||
"container_label_com_docker_compose_project_working_dir",
|
||||
"image",
|
||||
"instance",
|
||||
"name",
|
||||
"Value",
|
||||
"container_label_com_docker_compose_service"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {},
|
||||
"indexByName": {},
|
||||
"renameByName": {
|
||||
"Value": "Running",
|
||||
"container_label_com_docker_compose_project": "Label",
|
||||
"container_label_com_docker_compose_project_working_dir": "Working dir",
|
||||
"container_label_com_docker_compose_service": "Service",
|
||||
"image": "Registry Image",
|
||||
"instance": "Instance",
|
||||
"name": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "table"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 27,
|
||||
"style": "dark",
|
||||
"tags": ["cadvisor", "docker"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {},
|
||||
"datasource": "Prometheus",
|
||||
"definition": "label_values({__name__=~\"container.*\"},instance)",
|
||||
"description": null,
|
||||
"error": null,
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Host",
|
||||
"multi": false,
|
||||
"name": "host",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values({__name__=~\"container.*\"},instance)",
|
||||
"refId": "Prometheus-host-Variable-Query"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 5,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {},
|
||||
"datasource": "Prometheus",
|
||||
"definition": "label_values({__name__=~\"container.*\", instance=~\"$host\"},name)",
|
||||
"description": null,
|
||||
"error": null,
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Container",
|
||||
"multi": false,
|
||||
"name": "container",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values({__name__=~\"container.*\", instance=~\"$host\"},name)",
|
||||
"refId": "Prometheus-container-Variable-Query"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Cadvisor exporter",
|
||||
"uid": "pMEd7m0Mz",
|
||||
"version": 1,
|
||||
"description": "Simple exporter for cadvisor only"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://192.168.0.1:9090
|
||||
@@ -1,11 +0,0 @@
|
||||
groups:
|
||||
- name: AllInstances
|
||||
rules:
|
||||
- alert: InstanceDown
|
||||
expr: up == 0
|
||||
for: 1m
|
||||
annotations:
|
||||
title: 'Instance {{ $labels.instance }} down'
|
||||
description: '{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 1 minute.'
|
||||
labels:
|
||||
severity: 'critical'
|
||||
@@ -1,28 +0,0 @@
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: prometheus
|
||||
scrape_interval: 30s
|
||||
static_configs:
|
||||
- targets: ["localhost:9090"]
|
||||
|
||||
- job_name: node-exporter
|
||||
scrape_interval: 30s
|
||||
static_configs:
|
||||
- targets:
|
||||
["192.168.0.1:9100", "192.168.0.10:9100", "192.168.0.11:9100"]
|
||||
|
||||
- job_name: cadvisor
|
||||
scrape_interval: 30s
|
||||
static_configs:
|
||||
- targets: ["192.168.0.1:9101", "192.168.0.11:9101"]
|
||||
|
||||
rule_files:
|
||||
- prometheus_alerts_rules.yml
|
||||
|
||||
alerting:
|
||||
alertmanagers:
|
||||
- static_configs:
|
||||
- targets:
|
||||
- 192.168.0.1:9093
|
||||
@@ -1,85 +0,0 @@
|
||||
- name: Create Folder /srv/prometheus if not exist
|
||||
file:
|
||||
path: /srv/prometheus
|
||||
mode: 0755
|
||||
state: directory
|
||||
|
||||
- name: Create Folder /srv/grafana if not exist
|
||||
file:
|
||||
path: /srv/grafana
|
||||
mode: 0755
|
||||
state: directory
|
||||
|
||||
- name: Create Folder /srv/alertmanager if not exist
|
||||
file:
|
||||
path: /srv/alertmanager
|
||||
mode: 0755
|
||||
state: directory
|
||||
|
||||
- name: Create prometheus configuration file
|
||||
copy:
|
||||
dest: /srv/prometheus/prometheus.yml
|
||||
src: prometheus_main.yml
|
||||
mode: 0644
|
||||
|
||||
- name: Create prometheus alert configuration file
|
||||
copy:
|
||||
dest: /srv/prometheus/prometheus_alerts_rules.yml
|
||||
src: prometheus_alerts_rules.yml
|
||||
mode: 0644
|
||||
|
||||
- name: Create grafana configuration files
|
||||
copy:
|
||||
dest: /srv/
|
||||
src: grafana
|
||||
mode: 0644
|
||||
|
||||
- name: Create alertmanager configuration file
|
||||
template:
|
||||
dest: /srv/alertmanager/alertmanager.yml
|
||||
src: alertmanager/alertmanager.j2
|
||||
mode: 0644
|
||||
|
||||
- name: Create Prometheus container
|
||||
docker_container:
|
||||
name: prometheus
|
||||
restart_policy: always
|
||||
image: prom/prometheus:
|
||||
volumes:
|
||||
- /srv/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- /srv/prometheus/prometheus_alerts_rules.yml:/etc/prometheus/prometheus_alerts_rules.yml
|
||||
- prometheus_main_data:/prometheus
|
||||
command: >
|
||||
--config.file=/etc/prometheus/prometheus.yml
|
||||
--storage.tsdb.path=/prometheus
|
||||
--web.console.libraries=/etc/prometheus/console_libraries
|
||||
--web.console.templates=/etc/prometheus/consoles
|
||||
--web.enable-lifecycle
|
||||
published_ports: "9090:9090"
|
||||
|
||||
- name: Create Grafana container
|
||||
docker_container:
|
||||
name: grafana
|
||||
restart_policy: always
|
||||
image: grafana/grafana:
|
||||
volumes:
|
||||
- grafana-data:/var/lib/grafana
|
||||
- /srv/grafana/provisioning:/etc/grafana/provisioning
|
||||
- /srv/grafana/dashboards:/var/lib/grafana/dashboards
|
||||
env:
|
||||
GF_AUTH_ANONYMOUS_ENABLED: "true"
|
||||
GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin"
|
||||
published_ports: "3000:3000"
|
||||
|
||||
- name: Create Alertmanager container
|
||||
docker_container:
|
||||
name: alertmanager
|
||||
restart_policy: always
|
||||
image: prom/alertmanager:
|
||||
volumes:
|
||||
- alertmanager-data:/data
|
||||
- /srv/alertmanager:/config
|
||||
command: >
|
||||
--config.file=/config/alertmanager.yml
|
||||
--log.level=debug
|
||||
published_ports: "9093:9093"
|
||||
@@ -1,13 +0,0 @@
|
||||
route:
|
||||
receiver: "mail"
|
||||
repeat_interval: 4h
|
||||
group_by: [ alertname ]
|
||||
|
||||
receivers:
|
||||
- name: "mail"
|
||||
email_configs:
|
||||
- smarthost: "outlook.office365.com:587"
|
||||
auth_username: "test@padok.fr"
|
||||
auth_password: "{{ alertmanager_smtp_password }}"
|
||||
from: "test@padok.fr"
|
||||
to: "test@padok.fr"
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/ollama/defaults/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# ROCm
|
||||
ollama_rocm_version: "6.2" # ROCm release stream — update when upgrading
|
||||
ollama_rocm_packages:
|
||||
- hip-runtime-amd # Pulls in full ROCm stack
|
||||
- rocminfo # GPU visibility verification tool
|
||||
- rocm-smi-lib # ROCm system management interface
|
||||
|
||||
# Ollama
|
||||
ollama_version: "latest"
|
||||
ollama_port: 11434
|
||||
ollama_host: "0.0.0.0" # Listen on all interfaces for cluster access
|
||||
ollama_default_model: "qwen3:8b"
|
||||
|
||||
# HSA override — required for RX 5000-series (gfx1010 / RDNA 1)
|
||||
# Tells ROCm to treat the GPU as gfx1010 which has supported LLVM targets.
|
||||
ollama_hsa_override_gfx_version: "10.1.0"
|
||||
|
||||
# Data disk
|
||||
ollama_setup_data_disk: true
|
||||
ollama_data_disk: /dev/sdb
|
||||
ollama_data_dir: /var/lib/ollama # Ollama default models path
|
||||
ollama_data_vg: ollama-vg
|
||||
ollama_data_lv: ollama-lv
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/ollama/handlers/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: update initramfs
|
||||
command: update-initramfs -u
|
||||
|
||||
- name: reload systemd
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
|
||||
- name: restart ollama
|
||||
systemd:
|
||||
name: ollama
|
||||
state: restarted
|
||||
@@ -1,229 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/ollama/tasks/main.yml
|
||||
# DESCRIPTION: Deploys Ollama with AMD ROCm GPU acceleration on Ubuntu 24.04.
|
||||
# Handles:
|
||||
# - ROCm repository and driver stack installation
|
||||
# - Data disk preparation (LVM, ext4, persistent mount)
|
||||
# - Ollama installation and systemd service configuration
|
||||
# - HSA override for RX 5000-series (gfx1010) compatibility
|
||||
# - Initial model pull
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ROCm prerequisites
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Install ROCm prerequisite packages
|
||||
apt:
|
||||
name:
|
||||
- wget
|
||||
- gnupg
|
||||
- ca-certificates
|
||||
- lvm2
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Add ROCm apt repository signing key
|
||||
apt_key:
|
||||
url: https://repo.radeon.com/rocm/rocm.gpg.key
|
||||
state: present
|
||||
|
||||
- name: Add ROCm apt repository
|
||||
apt_repository:
|
||||
repo: "deb [arch=amd64] https://repo.radeon.com/rocm/apt/{{ ollama_rocm_version }} {{ ansible_distribution_release }} main"
|
||||
state: present
|
||||
filename: rocm
|
||||
notify: update apt cache
|
||||
|
||||
- name: Flush handlers to update apt cache before ROCm install
|
||||
meta: flush_handlers
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ROCm driver stack
|
||||
# hip-runtime-amd pulls in the full ROCm stack as a dependency.
|
||||
# rocminfo is used to verify GPU visibility after install.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Install ROCm packages
|
||||
apt:
|
||||
name: "{{ ollama_rocm_packages }}"
|
||||
state: present
|
||||
notify: update initramfs
|
||||
|
||||
- name: Create /opt/rocm symlink to versioned directory
|
||||
file:
|
||||
src: "/opt/rocm-{{ ollama_rocm_version }}.0"
|
||||
dest: /opt/rocm
|
||||
state: link
|
||||
force: false
|
||||
|
||||
- name: Add ROCm binaries to system PATH
|
||||
copy:
|
||||
dest: /etc/profile.d/rocm.sh
|
||||
content: |
|
||||
export PATH=$PATH:/opt/rocm/bin
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/rocm/lib
|
||||
mode: '0644'
|
||||
|
||||
- name: Add wed user to render and video groups for GPU access
|
||||
user:
|
||||
name: "{{ ansible_user }}"
|
||||
groups:
|
||||
- render
|
||||
- video
|
||||
append: yes
|
||||
|
||||
- name: Add ollama user to render and video groups (created by Ollama installer)
|
||||
user:
|
||||
name: ollama
|
||||
groups:
|
||||
- render
|
||||
- video
|
||||
append: yes
|
||||
ignore_errors: yes
|
||||
# ollama user does not exist yet at this point — Ollama installer creates it.
|
||||
# This task re-runs after install via the post-install handler.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Data disk — LVM setup on /dev/sdb
|
||||
# Follows the same pattern as the monitoring role (cinderella-castle).
|
||||
# Wipes any stale GPT/partition signatures before creating the PV to avoid
|
||||
# the "GPT protective partition" error encountered during monitoring deploy.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Wipe any existing partition signatures from data disk
|
||||
command: wipefs -a {{ ollama_data_disk }}
|
||||
args:
|
||||
creates: /dev/{{ ollama_data_vg }}
|
||||
when: ollama_setup_data_disk | bool
|
||||
|
||||
- name: Create LVM physical volume on data disk
|
||||
command: pvcreate {{ ollama_data_disk }}
|
||||
args:
|
||||
creates: /dev/{{ ollama_data_vg }}
|
||||
when: ollama_setup_data_disk | bool
|
||||
|
||||
- name: Create LVM volume group
|
||||
lvg:
|
||||
vg: "{{ ollama_data_vg }}"
|
||||
pvs: "{{ ollama_data_disk }}"
|
||||
when: ollama_setup_data_disk | bool
|
||||
|
||||
- name: Create LVM logical volume using 100% of VG
|
||||
lvol:
|
||||
vg: "{{ ollama_data_vg }}"
|
||||
lv: "{{ ollama_data_lv }}"
|
||||
size: 100%FREE
|
||||
shrink: false
|
||||
when: ollama_setup_data_disk | bool
|
||||
|
||||
- name: Format logical volume as ext4
|
||||
filesystem:
|
||||
fstype: ext4
|
||||
dev: "/dev/{{ ollama_data_vg }}/{{ ollama_data_lv }}"
|
||||
opts: "-L ollama-data"
|
||||
when: ollama_setup_data_disk | bool
|
||||
|
||||
- name: Create Ollama model directory mount point
|
||||
file:
|
||||
path: "{{ ollama_data_dir }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Mount Ollama data volume and add fstab entry
|
||||
mount:
|
||||
path: "{{ ollama_data_dir }}"
|
||||
src: "/dev/{{ ollama_data_vg }}/{{ ollama_data_lv }}"
|
||||
fstype: ext4
|
||||
opts: defaults,noatime
|
||||
state: mounted
|
||||
when: ollama_setup_data_disk | bool
|
||||
# noatime: eliminates inode access time writes on every model file read.
|
||||
# Meaningful win for large sequential reads like model loading.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Ollama installation
|
||||
# Uses the official install script which creates the ollama user and
|
||||
# systemd service unit. We override the service with our own config.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Download Ollama install script
|
||||
get_url:
|
||||
url: https://ollama.ai/install.sh
|
||||
dest: /tmp/ollama_install.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Run Ollama install script
|
||||
command: /tmp/ollama_install.sh
|
||||
environment: "{{ {} if ollama_version == 'latest' else {'OLLAMA_VERSION': ollama_version} }}"
|
||||
args:
|
||||
creates: /usr/local/bin/ollama
|
||||
|
||||
- name: Add ollama user to render and video groups (post-install)
|
||||
user:
|
||||
name: ollama
|
||||
groups:
|
||||
- render
|
||||
- video
|
||||
append: yes
|
||||
|
||||
- name: Set ownership of Ollama data directory to ollama user
|
||||
file:
|
||||
path: "{{ ollama_data_dir }}"
|
||||
owner: ollama
|
||||
group: ollama
|
||||
recurse: yes
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Systemd service override
|
||||
# Injects HSA_OVERRIDE_GFX_VERSION for RX 5000-series compatibility and
|
||||
# sets OLLAMA_MODELS to the data disk mount point.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Create systemd override directory for ollama service
|
||||
file:
|
||||
path: /etc/systemd/system/ollama.service.d
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Deploy ollama systemd service override
|
||||
template:
|
||||
src: ollama-override.conf.j2
|
||||
dest: /etc/systemd/system/ollama.service.d/override.conf
|
||||
mode: '0644'
|
||||
notify:
|
||||
- reload systemd
|
||||
- restart ollama
|
||||
|
||||
- name: Flush handlers to apply service config before model pull
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Enable and start Ollama service
|
||||
systemd:
|
||||
name: ollama
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
|
||||
- name: Wait for Ollama API to become available
|
||||
uri:
|
||||
url: "http://localhost:{{ ollama_port }}/api/tags"
|
||||
status_code: 200
|
||||
register: ollama_health
|
||||
retries: 12
|
||||
delay: 5
|
||||
until: ollama_health.status == 200
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Initial model pull
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Pull initial model
|
||||
command: ollama pull {{ ollama_default_model }}
|
||||
environment:
|
||||
OLLAMA_HOST: "http://localhost:{{ ollama_port }}"
|
||||
register: model_pull
|
||||
changed_when: "'pulled' in model_pull.stdout or model_pull.rc == 0"
|
||||
timeout: 600
|
||||
# Qwen3 8B Q4_K_M is ~5GB — allow 10 minutes on first pull.
|
||||
@@ -1,13 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/ollama/templates/ollama-override.conf.j2
|
||||
# DESCRIPTION: Systemd drop-in for the ollama service.
|
||||
# Sets environment variables required for AMD GPU operation
|
||||
# and configures the model storage path.
|
||||
# Managed by Ansible — do not edit manually.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
[Service]
|
||||
Environment="OLLAMA_HOST={{ ollama_host }}:{{ ollama_port }}"
|
||||
Environment="OLLAMA_MODELS={{ ollama_data_dir }}"
|
||||
Environment="HSA_OVERRIDE_GFX_VERSION={{ ollama_hsa_override_gfx_version }}"
|
||||
Environment="ROCR_VISIBLE_DEVICES=0"
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
# Proxmox Authentik OIDC Configuration
|
||||
proxmox_oidc_realm_name: "authentik"
|
||||
proxmox_oidc_issuer_url: "https://authentik.local.mk-labs.cloud/application/o/proxmox/"
|
||||
proxmox_oidc_username_claim: "username"
|
||||
proxmox_oidc_scopes: "openid email profile"
|
||||
proxmox_oidc_autocreate: true
|
||||
proxmox_oidc_default_realm: false
|
||||
proxmox_oidc_comment: "Authentik SSO"
|
||||
|
||||
# Client credentials - override via vault
|
||||
proxmox_oidc_client_id: "{{ vault_proxmox_oidc_client_id }}"
|
||||
proxmox_oidc_client_key: "{{ vault_proxmox_oidc_client_key }}"
|
||||
|
||||
# ACL mappings for Authentik users
|
||||
# Format: list of dicts with path, user or group, and role
|
||||
proxmox_oidc_acl_entries: []
|
||||
# Example:
|
||||
# - path: "/"
|
||||
# user: "rblundon@authentik"
|
||||
# role: "Administrator"
|
||||
# - path: "/"
|
||||
# group: "admins-authentik"
|
||||
# role: "Administrator"
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
# ansible/roles/proxmox/tasks/main.yml
|
||||
#
|
||||
# Main entry point for the proxmox role.
|
||||
# Add existing tasks above the OIDC include as needed.
|
||||
|
||||
- name: Configure Authentik OIDC authentication
|
||||
ansible.builtin.include_tasks: oidc.yml
|
||||
tags:
|
||||
- proxmox-oidc
|
||||
- oidc
|
||||
- authentik
|
||||
@@ -1,59 +0,0 @@
|
||||
---
|
||||
# ansible/roles/proxmox/tasks/oidc.yml
|
||||
#
|
||||
# Configures Proxmox to authenticate against Authentik via OpenID Connect.
|
||||
# This only needs to run on ONE node in the cluster since realm config
|
||||
# is stored in /etc/pve/domains.cfg (cluster-wide via pmxcfs).
|
||||
|
||||
- name: Check if OIDC realm already exists
|
||||
ansible.builtin.command:
|
||||
cmd: pveum realm list --output-format json
|
||||
register: proxmox_realms
|
||||
changed_when: false
|
||||
|
||||
- name: Parse existing realms
|
||||
ansible.builtin.set_fact:
|
||||
proxmox_realm_exists: >-
|
||||
{{ proxmox_realms.stdout | from_json | selectattr('realm', 'equalto', proxmox_oidc_realm_name) | list | length > 0 }}
|
||||
|
||||
- name: Add OIDC realm for Authentik
|
||||
ansible.builtin.shell: >-
|
||||
pveum realm add {{ proxmox_oidc_realm_name }}
|
||||
--type openid
|
||||
--issuer-url '{{ proxmox_oidc_issuer_url }}'
|
||||
--client-id '{{ proxmox_oidc_client_id }}'
|
||||
--client-key '{{ proxmox_oidc_client_key }}'
|
||||
--username-claim '{{ proxmox_oidc_username_claim }}'
|
||||
--scopes '{{ proxmox_oidc_scopes }}'
|
||||
--autocreate {{ proxmox_oidc_autocreate | ternary('1', '0') }}
|
||||
--comment '{{ proxmox_oidc_comment }}'
|
||||
{% if proxmox_oidc_default_realm %}--default 1{% endif %}
|
||||
when: not proxmox_realm_exists
|
||||
no_log: true
|
||||
|
||||
- name: Update OIDC realm for Authentik
|
||||
ansible.builtin.shell: >-
|
||||
pveum realm modify {{ proxmox_oidc_realm_name }}
|
||||
--issuer-url '{{ proxmox_oidc_issuer_url }}'
|
||||
--client-id '{{ proxmox_oidc_client_id }}'
|
||||
--client-key '{{ proxmox_oidc_client_key }}'
|
||||
--scopes '{{ proxmox_oidc_scopes }}'
|
||||
--autocreate {{ proxmox_oidc_autocreate | ternary('1', '0') }}
|
||||
--comment '{{ proxmox_oidc_comment }}'
|
||||
{% if proxmox_oidc_default_realm %}--default 1{% endif %}
|
||||
when: proxmox_realm_exists
|
||||
no_log: true
|
||||
|
||||
# --- ACL Configuration ---
|
||||
|
||||
- name: Configure ACL entries for OIDC users
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
pveum acl modify {{ item.path }}
|
||||
{% if item.user is defined %}--users {{ item.user }}{% endif %}
|
||||
{% if item.group is defined %}--groups {{ item.group }}{% endif %}
|
||||
--roles {{ item.role }}
|
||||
loop: "{{ proxmox_oidc_acl_entries }}"
|
||||
loop_control:
|
||||
label: "{{ item.user | default(item.group) }} -> {{ item.role }} on {{ item.path }}"
|
||||
when: proxmox_oidc_acl_entries | length > 0
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
# file: roles/semaphore/defaults/main.yml
|
||||
|
||||
semaphore_compose_dir: /opt/docker/semaphore
|
||||
semaphore_ssh_key_file: "~/.ssh/ansible"
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# file: roles/semaphore/handlers/main.yml
|
||||
|
||||
- name: Restart Semaphore
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ semaphore_compose_dir }}"
|
||||
state: restarted
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
# file: roles/semaphore/tasks/main.yml
|
||||
|
||||
- name: Create Semaphore directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ semaphore_compose_dir }}"
|
||||
state: directory
|
||||
owner: wed
|
||||
group: docker
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy Compose file from boilerplate
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/semaphore/compose.yaml"
|
||||
dest: "{{ semaphore_compose_dir }}/compose.yaml"
|
||||
owner: wed
|
||||
group: docker
|
||||
mode: '0644'
|
||||
|
||||
- name: Deploy Semaphore .env file
|
||||
ansible.builtin.template:
|
||||
src: env.j2
|
||||
dest: "{{ semaphore_compose_dir }}/.env"
|
||||
owner: wed
|
||||
group: docker
|
||||
mode: '0600'
|
||||
|
||||
- name: Copy SSH key for Ansible authentication
|
||||
ansible.builtin.copy:
|
||||
src: "{{ semaphore_ssh_key_file }}"
|
||||
dest: "{{ semaphore_compose_dir }}/ansible_key"
|
||||
owner: "1001"
|
||||
group: "1001"
|
||||
mode: '0600'
|
||||
|
||||
- name: Start Semaphore containers
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ semaphore_compose_dir }}"
|
||||
state: present
|
||||
register: semaphore_compose
|
||||
|
||||
- name: Wait for Semaphore to be ready
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:3000/api/ping"
|
||||
method: GET
|
||||
status_code: 200
|
||||
register: semaphore_health
|
||||
retries: 48
|
||||
delay: 5
|
||||
until: semaphore_health.status == 200
|
||||
@@ -1,3 +0,0 @@
|
||||
DATABASE_PASSWORD={{ vault_semaphore_database_password }}
|
||||
SEMAPHORE_ADMIN_PASSWORD={{ vault_semaphore_admin_password }}
|
||||
SEMAPHORE_ACCESS_KEY_ENCRYPTION={{ vault_semaphore_access_key_encryption }}
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
# file: roles/step-ca/defaults/main.yml
|
||||
|
||||
# Docker image
|
||||
stepca_image: "smallstep/step-ca"
|
||||
stepca_image_tag: "latest"
|
||||
stepca_container_name: "step-ca"
|
||||
|
||||
# Paths
|
||||
stepca_base_dir: "/opt/docker/step-ca"
|
||||
stepca_data_dir: "{{ stepca_base_dir }}/data"
|
||||
|
||||
# CA configuration
|
||||
stepca_ca_name: "mk-labs CA"
|
||||
stepca_listen_port: 9000
|
||||
stepca_ssh_enabled: true
|
||||
stepca_remote_management: true
|
||||
|
||||
# Network
|
||||
stepca_docker_network: "proxy"
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# file: roles/step-ca/handlers/main.yml
|
||||
|
||||
- name: restart step-ca
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ stepca_base_dir }}"
|
||||
state: restarted
|
||||
@@ -1,123 +0,0 @@
|
||||
---
|
||||
# file: roles/step-ca/tasks/main.yml
|
||||
|
||||
- name: Create step-ca base directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ stepca_base_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Create step-ca data directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ stepca_data_dir }}"
|
||||
state: directory
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0755"
|
||||
|
||||
- name: Create Docker network for step-ca
|
||||
community.docker.docker_network:
|
||||
name: "{{ stepca_docker_network }}"
|
||||
state: present
|
||||
|
||||
- name: Deploy step-ca compose file
|
||||
ansible.builtin.template:
|
||||
src: compose.yaml.j2
|
||||
dest: "{{ stepca_base_dir }}/compose.yaml"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: restart step-ca
|
||||
|
||||
- name: Deploy step-ca environment file
|
||||
ansible.builtin.template:
|
||||
src: env.j2
|
||||
dest: "{{ stepca_base_dir }}/.env"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
notify: restart step-ca
|
||||
|
||||
- name: Create step-ca secrets directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ stepca_data_dir }}/secrets"
|
||||
state: directory
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0700"
|
||||
|
||||
- name: Write CA password file
|
||||
ansible.builtin.copy:
|
||||
content: "{{ stepca_ca_password }}"
|
||||
dest: "{{ stepca_data_dir }}/secrets/password"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
|
||||
- name: Check if step-ca is already initialized
|
||||
ansible.builtin.stat:
|
||||
path: "{{ stepca_data_dir }}/config/ca.json"
|
||||
register: stepca_config
|
||||
|
||||
- name: Initialize step-ca (first run only)
|
||||
when: not stepca_config.stat.exists
|
||||
block:
|
||||
- name: Run step-ca initialization
|
||||
community.docker.docker_container:
|
||||
name: "{{ stepca_container_name }}-init"
|
||||
image: "{{ stepca_image }}:{{ stepca_image_tag }}"
|
||||
command: >-
|
||||
step ca init
|
||||
--name "{{ stepca_ca_name }}"
|
||||
--dns "{{ stepca_dns_names }}"
|
||||
--address ":{{ stepca_listen_port }}"
|
||||
--provisioner "admin"
|
||||
--password-file /home/step/secrets/password
|
||||
--ssh
|
||||
--remote-management
|
||||
volumes:
|
||||
- "{{ stepca_data_dir }}:/home/step"
|
||||
state: started
|
||||
detach: false
|
||||
cleanup: true
|
||||
register: stepca_init
|
||||
|
||||
- name: Display CA fingerprint
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ stepca_init.container.Output }}"
|
||||
when: stepca_init is changed
|
||||
|
||||
- name: Start step-ca
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ stepca_base_dir }}"
|
||||
state: present
|
||||
|
||||
- name: Template OIDC provisioner patch script
|
||||
ansible.builtin.template:
|
||||
src: patch_oidc_provisioner.py.j2
|
||||
dest: "{{ stepca_base_dir }}/patch_oidc_provisioner.py"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0700"
|
||||
no_log: true
|
||||
|
||||
- name: Patch OIDC provisioner in ca.json
|
||||
ansible.builtin.command:
|
||||
cmd: python3 {{ stepca_base_dir }}/patch_oidc_provisioner.py
|
||||
register: oidc_patch_result
|
||||
changed_when: "'CHANGED' in oidc_patch_result.stdout"
|
||||
|
||||
- name: Restart step-ca if OIDC provisioner changed
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ stepca_base_dir }}"
|
||||
state: restarted
|
||||
when: oidc_patch_result is changed
|
||||
|
||||
- name: Clean up patch script
|
||||
ansible.builtin.file:
|
||||
path: "{{ stepca_base_dir }}/patch_oidc_provisioner.py"
|
||||
state: absent
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
# Managed by Ansible — do not edit manually
|
||||
# Smallstep step-ca — SSH & X.509 Certificate Authority
|
||||
|
||||
services:
|
||||
step-ca:
|
||||
image: {{ stepca_image }}:{{ stepca_image_tag }}
|
||||
container_name: {{ stepca_container_name }}
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "{{ stepca_listen_port }}:{{ stepca_listen_port }}"
|
||||
volumes:
|
||||
- {{ stepca_data_dir }}:/home/step
|
||||
environment:
|
||||
- DOCKER_STEPCA_INIT_NAME={{ stepca_ca_name }}
|
||||
- DOCKER_STEPCA_INIT_DNS_NAMES={{ stepca_dns_names }}
|
||||
- DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT={{ stepca_remote_management | lower }}
|
||||
- DOCKER_STEPCA_INIT_SSH={{ stepca_ssh_enabled | lower }}
|
||||
networks:
|
||||
- {{ stepca_docker_network }}
|
||||
|
||||
networks:
|
||||
{{ stepca_docker_network }}:
|
||||
external: true
|
||||
@@ -1,4 +0,0 @@
|
||||
# Managed by Ansible — do not edit manually
|
||||
STEPCA_DNS_NAMES={{ stepca_dns_names }}
|
||||
STEPCA_SSH_ENABLED={{ stepca_ssh_enabled | lower }}
|
||||
STEPCA_REMOTE_MANAGEMENT={{ stepca_remote_management | lower }}
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Managed by Ansible — do not edit manually
|
||||
# Patches the OIDC provisioner in step-ca's ca.json with values from Ansible vars
|
||||
import json
|
||||
import sys
|
||||
|
||||
ca_json_path = "{{ stepca_data_dir }}/config/ca.json"
|
||||
provisioner_name = "{{ stepca_oidc_provisioner_name }}"
|
||||
desired_domains = {{ stepca_oidc_domains | to_json }}
|
||||
desired_client_id = "{{ stepca_oidc_client_id }}"
|
||||
desired_client_secret = "{{ stepca_oidc_client_secret }}"
|
||||
desired_config_endpoint = "{{ stepca_oidc_configuration_endpoint }}"
|
||||
desired_listen_address = "{{ stepca_oidc_listen_address }}"
|
||||
|
||||
with open(ca_json_path) as f:
|
||||
cfg = json.load(f)
|
||||
|
||||
changed = False
|
||||
found = False
|
||||
|
||||
for p in cfg["authority"]["provisioners"]:
|
||||
if p.get("name") == provisioner_name and p.get("type") == "OIDC":
|
||||
found = True
|
||||
updates = {
|
||||
"domains": desired_domains,
|
||||
"clientID": desired_client_id,
|
||||
"clientSecret": desired_client_secret,
|
||||
"configurationEndpoint": desired_config_endpoint,
|
||||
"listenAddress": desired_listen_address,
|
||||
}
|
||||
for key, value in updates.items():
|
||||
if p.get(key) != value:
|
||||
p[key] = value
|
||||
changed = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
# Add the OIDC provisioner if it doesn't exist
|
||||
cfg["authority"]["provisioners"].append({
|
||||
"type": "OIDC",
|
||||
"name": provisioner_name,
|
||||
"clientID": desired_client_id,
|
||||
"clientSecret": desired_client_secret,
|
||||
"configurationEndpoint": desired_config_endpoint,
|
||||
"domains": desired_domains,
|
||||
"listenAddress": desired_listen_address,
|
||||
"claims": {"enableSSHCA": True},
|
||||
"options": {"x509": {}, "ssh": {}},
|
||||
})
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
with open(ca_json_path, "w") as f:
|
||||
json.dump(cfg, f, indent=8)
|
||||
print("CHANGED")
|
||||
else:
|
||||
print("OK")
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/talosctl/defaults/main.yml
|
||||
# DESCRIPTION: Default variables for talosctl installation.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Pin to a specific version for reproducibility.
|
||||
# Update this when upgrading the fastpass cluster.
|
||||
talosctl_version: "v1.12.4"
|
||||
|
||||
# Architecture of the target host (amd64 or arm64)
|
||||
talosctl_arch: "amd64"
|
||||
|
||||
# Where to install the binary
|
||||
talosctl_install_dir: "/usr/local/bin"
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/talosctl/tasks/main.yml
|
||||
# DESCRIPTION: Install talosctl binary from GitHub releases.
|
||||
# Downloads a pinned version, installs to /usr/local/bin,
|
||||
# and verifies the installation.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Check if talosctl is already installed
|
||||
stat:
|
||||
path: "{{ talosctl_install_dir }}/talosctl"
|
||||
register: talosctl_binary
|
||||
|
||||
- name: Get installed talosctl version
|
||||
command: "{{ talosctl_install_dir }}/talosctl version --client --short"
|
||||
register: talosctl_installed_version
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: talosctl_binary.stat.exists
|
||||
|
||||
- name: Install talosctl
|
||||
when: >
|
||||
not talosctl_binary.stat.exists or
|
||||
talosctl_version not in (talosctl_installed_version.stdout | default(''))
|
||||
block:
|
||||
- name: Download talosctl {{ talosctl_version }}
|
||||
get_url:
|
||||
url: "https://github.com/siderolabs/talos/releases/download/{{ talosctl_version }}/talosctl-linux-{{ talosctl_arch }}"
|
||||
dest: "/tmp/talosctl"
|
||||
mode: "0755"
|
||||
|
||||
- name: Install talosctl to {{ talosctl_install_dir }}
|
||||
copy:
|
||||
src: "/tmp/talosctl"
|
||||
dest: "{{ talosctl_install_dir }}/talosctl"
|
||||
mode: "0755"
|
||||
remote_src: true
|
||||
|
||||
- name: Clean up downloaded binary
|
||||
file:
|
||||
path: "/tmp/talosctl"
|
||||
state: absent
|
||||
|
||||
- name: Verify talosctl installation
|
||||
command: "{{ talosctl_install_dir }}/talosctl version --client --short"
|
||||
register: talosctl_verify
|
||||
changed_when: false
|
||||
|
||||
- name: Display installed talosctl version
|
||||
debug:
|
||||
msg: "talosctl installed: {{ talosctl_verify.stdout }}"
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/talosctl/meta/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
galaxy_info:
|
||||
author: rblundon
|
||||
description: Install talosctl CLI for managing TalosOS clusters
|
||||
license: MIT
|
||||
min_ansible_version: "2.14"
|
||||
platforms:
|
||||
- name: Ubuntu
|
||||
versions:
|
||||
- noble
|
||||
|
||||
dependencies:
|
||||
- role: common
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
node_exporter_version: v1.4.0
|
||||
cadvisor_version: v0.46.0
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
- name: Create NodeExporter
|
||||
docker_container:
|
||||
name: node-exporter
|
||||
restart_policy: always
|
||||
image: prom/node-exporter:{{ node_exporter_version }}
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
command: >
|
||||
--path.procfs=/host/proc
|
||||
--path.rootfs=/rootfs
|
||||
--path.sysfs=/host/sys
|
||||
--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)
|
||||
published_ports: "9100:9100"
|
||||
|
||||
- name: Create cAdvisor
|
||||
docker_container:
|
||||
name: cadvisor
|
||||
restart_policy: always
|
||||
image: gcr.io/cadvisor/cadvisor:{{ cadvisor_version }}
|
||||
volumes:
|
||||
- /:/rootfs:ro
|
||||
- /var/run:/var/run:ro
|
||||
- /sys:/sys:ro
|
||||
- /var/lib/docker/:/var/lib/docker:ro
|
||||
- /dev/disk/:/dev/disk:ro
|
||||
published_ports: "9101:8080"
|
||||
@@ -1,38 +0,0 @@
|
||||
Role Name
|
||||
=========
|
||||
|
||||
A brief description of the role goes here.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
|
||||
|
||||
Role Variables
|
||||
--------------
|
||||
|
||||
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
|
||||
|
||||
Example Playbook
|
||||
----------------
|
||||
|
||||
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
|
||||
|
||||
- hosts: servers
|
||||
roles:
|
||||
- { role: username.rolename, x: 42 }
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
BSD
|
||||
|
||||
Author Information
|
||||
------------------
|
||||
|
||||
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
|
||||
@@ -1,3 +0,0 @@
|
||||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# defaults file for time-sync
|
||||
@@ -1,3 +0,0 @@
|
||||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# handlers file for time-sync
|
||||
@@ -1,35 +0,0 @@
|
||||
#SPDX-License-Identifier: MIT-0
|
||||
galaxy_info:
|
||||
author: your name
|
||||
description: your role description
|
||||
company: your company (optional)
|
||||
|
||||
# If the issue tracker for your role is not on github, uncomment the
|
||||
# next line and provide a value
|
||||
# issue_tracker_url: http://example.com/issue/tracker
|
||||
|
||||
# Choose a valid license ID from https://spdx.org - some suggested licenses:
|
||||
# - BSD-3-Clause (default)
|
||||
# - MIT
|
||||
# - GPL-2.0-or-later
|
||||
# - GPL-3.0-only
|
||||
# - Apache-2.0
|
||||
# - CC-BY-4.0
|
||||
license: license (GPL-2.0-or-later, MIT, etc)
|
||||
|
||||
min_ansible_version: 2.1
|
||||
|
||||
# If this a Container Enabled role, provide the minimum Ansible Container version.
|
||||
# min_ansible_container_version:
|
||||
|
||||
galaxy_tags: []
|
||||
# List tags for your role here, one per line. A tag is a keyword that describes
|
||||
# and categorizes the role. Users find roles by searching for tags. Be sure to
|
||||
# remove the '[]' above, if you add tags to this list.
|
||||
#
|
||||
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
|
||||
# Maximum 20 tags per role.
|
||||
|
||||
dependencies: []
|
||||
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
|
||||
# if you add dependencies to this list.
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
# tasks file for time-sync
|
||||
- name: Manage Time Synchronization
|
||||
become: true
|
||||
block:
|
||||
- name: Remove the ntp package
|
||||
ansible.builtin.package:
|
||||
name: ntp
|
||||
state: absent
|
||||
|
||||
- name: Install the chrony package
|
||||
ansible.builtin.package:
|
||||
name: chrony
|
||||
state: present
|
||||
|
||||
- name: Remove pool entries from Chrony config file
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/chrony/chrony.conf
|
||||
regexp: '^pool\s*'
|
||||
state: absent
|
||||
|
||||
- name: Add local NTP servers to Chrony config file
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/chrony/chrony.conf
|
||||
line: "server 10.1.71.21 iburst"
|
||||
state: present
|
||||
|
||||
- name: Start and enable chronyd
|
||||
ansible.builtin.systemd:
|
||||
name: chronyd
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Force time synchronization
|
||||
become: true
|
||||
ansible.builtin.command: chronyc makestep
|
||||
register: chronyc_status
|
||||
|
||||
- name: Display chronyc status
|
||||
ansible.builtin.debug:
|
||||
var: chronyc_status.stdout
|
||||
@@ -1,3 +0,0 @@
|
||||
#SPDX-License-Identifier: MIT-0
|
||||
localhost
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
- hosts: localhost
|
||||
remote_user: root
|
||||
roles:
|
||||
- time-sync
|
||||
@@ -1,3 +0,0 @@
|
||||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# vars file for time-sync
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/traefik/defaults/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Directory on the host where Traefik files are deployed
|
||||
traefik_base_dir: /opt/docker/traefik
|
||||
|
||||
# Cloudflare API token — MUST be set in vault
|
||||
traefik_cloudflare_api_token: ""
|
||||
@@ -1,9 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/traefik/handlers/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: restart traefik
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ traefik_base_dir }}"
|
||||
state: present
|
||||
recreate: always
|
||||
@@ -1,55 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/traefik/tasks/main.yml
|
||||
# DESCRIPTION: Deploys Traefik from boilerplate-generated files in the repo.
|
||||
# The compose.yaml and dynamic config live in boilerplates/traefik/
|
||||
# and are copied to the host. Only the .env file is templated
|
||||
# to inject secrets from Ansible vault.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Create Traefik directory structure
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: '0775'
|
||||
loop:
|
||||
- "{{ traefik_base_dir }}"
|
||||
- "{{ traefik_base_dir }}/dynamic"
|
||||
|
||||
- name: Copy compose.yaml from boilerplate
|
||||
copy:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/traefik/compose.yaml"
|
||||
dest: "{{ traefik_base_dir }}/compose.yaml"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: '0644'
|
||||
notify: restart traefik
|
||||
|
||||
- name: Copy dynamic configuration from boilerplate
|
||||
copy:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/traefik/dynamic/"
|
||||
dest: "{{ traefik_base_dir }}/dynamic/"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: '0644'
|
||||
notify: restart traefik
|
||||
|
||||
- name: Deploy .env with secrets from vault
|
||||
template:
|
||||
src: env.j2
|
||||
dest: "{{ traefik_base_dir }}/.env"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: '0600'
|
||||
notify: restart traefik
|
||||
|
||||
- name: Create Traefik Docker network
|
||||
docker_network:
|
||||
name: proxy
|
||||
state: present
|
||||
|
||||
- name: Start Traefik
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ traefik_base_dir }}"
|
||||
state: present
|
||||
@@ -1,3 +0,0 @@
|
||||
# Managed by Ansible — do not edit manually
|
||||
CF_DNS_API_TOKEN={{ traefik_cloudflare_api_token }}
|
||||
CLOUDFLARE_TUNNEL_TOKEN={{ vault_traefik_tunnel_token }}
|
||||
Reference in New Issue
Block a user