Remove dups
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
---
|
||||
- name: "Activate Checkmk changes"
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
vars_files:
|
||||
- secrets.yaml
|
||||
vars:
|
||||
server_url: "http://192.168.2.4:5000"
|
||||
site: "cmk"
|
||||
|
||||
tasks:
|
||||
- name: "Start activation on a specific site"
|
||||
checkmk.general.activation:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
force_foreign_changes: 'true'
|
||||
sites:
|
||||
- "{{ site }}"
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
- name: Install Checkmk agent on all hosts
|
||||
hosts: all
|
||||
become: true
|
||||
roles:
|
||||
- checkmk.general.agent
|
||||
vars:
|
||||
checkmk_agent_version: "2.4.0p15"
|
||||
checkmk_agent_server: << checkmk_server >>
|
||||
checkmk_agent_server_protocol: << checkmk_protocol >>
|
||||
checkmk_agent_site: << checkmk_site >>
|
||||
checkmk_agent_auto_activate: << checkmk_auto_activate >>
|
||||
checkmk_agent_tls: << checkmk_tls >>
|
||||
checkmk_agent_user: << checkmk_user >>
|
||||
checkmk_agent_pass: << checkmk_pass >>
|
||||
checkmk_agent_host_name: << checkmk_host >>
|
||||
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"slug": "checkmk-install-agent",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Install Checkmk Agent",
|
||||
"description": "Installs and registers the Checkmk monitoring agent on target hosts using the checkmk.general.agent Ansible role.",
|
||||
"tags": [],
|
||||
"icon": {
|
||||
"provider": "selfhst",
|
||||
"id": "checkmk"
|
||||
},
|
||||
"draft": false,
|
||||
"version": {
|
||||
"name": "2.4.0",
|
||||
"source_dep_name": "manual/checkmk-install-agent"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Checkmk Configuration",
|
||||
"name": "checkmk",
|
||||
"items": [
|
||||
{
|
||||
"name": "checkmk_server",
|
||||
"type": "str",
|
||||
"title": "Checkmk Server",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "checkmk_protocol",
|
||||
"type": "str",
|
||||
"title": "Protocol",
|
||||
"required": true,
|
||||
"default": "https",
|
||||
"config": {
|
||||
"placeholder": "https"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "checkmk_site",
|
||||
"type": "str",
|
||||
"title": "Checkmk Site",
|
||||
"required": true,
|
||||
"default": "cmk",
|
||||
"config": {
|
||||
"placeholder": "cmk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "checkmk_auto_activate",
|
||||
"type": "bool",
|
||||
"title": "Auto Activate Agent",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "checkmk_tls",
|
||||
"type": "bool",
|
||||
"title": "TLS",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "checkmk_user",
|
||||
"type": "str",
|
||||
"title": "Checkmk Automation User",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "checkmk_pass",
|
||||
"type": "secret",
|
||||
"title": "Automation Password",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "checkmk_host",
|
||||
"type": "str",
|
||||
"title": "Checkmk Host Name",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
- name: Manage Checkmk host
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: "Create or update host in Checkmk"
|
||||
checkmk.general.host:
|
||||
server_url: "<< checkmk_protocol >>://<< checkmk_server >>"
|
||||
site: << checkmk_site >>
|
||||
automation_user: << checkmk_user >>
|
||||
automation_secret: << checkmk_pass >>
|
||||
name: << host_name >>
|
||||
attributes:
|
||||
ipaddress: << host_ip >>
|
||||
folder: << host_folder >>
|
||||
state: "present"
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
@@ -1,94 +0,0 @@
|
||||
{
|
||||
"slug": "checkmk-manage-host",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Manage Checkmk Host",
|
||||
"description": "Manages host entries in Checkmk monitoring using the checkmk.general.host Ansible module. Creates or updates host configuration in your Checkmk instance.",
|
||||
"tags": [],
|
||||
"icon": {
|
||||
"provider": "selfhst",
|
||||
"id": "checkmk"
|
||||
},
|
||||
"draft": false,
|
||||
"version": {
|
||||
"name": "2.4.0",
|
||||
"source_dep_name": "manual/checkmk-manage-host"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Checkmk Configuration",
|
||||
"name": "checkmk",
|
||||
"items": [
|
||||
{
|
||||
"name": "checkmk_server",
|
||||
"type": "str",
|
||||
"title": "Checkmk Server",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "checkmk_protocol",
|
||||
"type": "str",
|
||||
"title": "Protocol",
|
||||
"required": true,
|
||||
"default": "https",
|
||||
"config": {
|
||||
"placeholder": "https"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "checkmk_site",
|
||||
"type": "str",
|
||||
"title": "Checkmk Site",
|
||||
"required": true,
|
||||
"default": "cmk",
|
||||
"config": {
|
||||
"placeholder": "cmk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "checkmk_user",
|
||||
"type": "str",
|
||||
"title": "Checkmk Automation User",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "checkmk_pass",
|
||||
"type": "secret",
|
||||
"title": "Automation Password",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Host Configuration",
|
||||
"name": "host",
|
||||
"items": [
|
||||
{
|
||||
"name": "host_name",
|
||||
"type": "str",
|
||||
"title": "Host Name",
|
||||
"required": true,
|
||||
"description": "Hostname to add to Checkmk"
|
||||
},
|
||||
{
|
||||
"name": "host_ip",
|
||||
"type": "str",
|
||||
"title": "Host IP",
|
||||
"required": true,
|
||||
"description": "IP address of the host"
|
||||
},
|
||||
{
|
||||
"name": "host_folder",
|
||||
"type": "str",
|
||||
"title": "Folder Path",
|
||||
"required": true,
|
||||
"default": "/",
|
||||
"config": {
|
||||
"placeholder": "/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
# to run: ansible-playbook -i ./inventory.ini install-agent.yaml --ask-become-pass
|
||||
---
|
||||
- name: "Install Checkmk agent on all hosts"
|
||||
hosts: all
|
||||
become: yes
|
||||
roles:
|
||||
- checkmk.general.agent
|
||||
vars:
|
||||
checkmk_agent_version: "2.3.0p7"
|
||||
#checkmk_agent_server: "192.168.2.4:5000"
|
||||
checkmk_agent_server: "checkmk.mikemcfetridge.com"
|
||||
checkmk_agent_server_protocol: https
|
||||
checkmk_agent_site: "cmk"
|
||||
checkmk_agent_auto_activate: true
|
||||
checkmk_agent_tls: "true" # NOTE: Register Agent to enable TLS
|
||||
checkmk_agent_user: "{{ automation_user }}"
|
||||
checkmk_agent_pass: "{{ automation_secret }}"
|
||||
checkmk_agent_host_name: "{{ ansible_hostname }}" # NOTE: Required to replace FQDN with hostname only
|
||||
vars_files:
|
||||
- secrets.yaml
|
||||
@@ -1,23 +0,0 @@
|
||||
[servers]
|
||||
192.168.2.1
|
||||
192.168.2.2
|
||||
192.168.2.3
|
||||
192.168.2.4
|
||||
192.168.2.5
|
||||
192.168.2.6
|
||||
192.168.2.7
|
||||
192.168.2.8
|
||||
192.168.2.9
|
||||
192.168.2.10
|
||||
192.168.2.11
|
||||
192.168.2.12
|
||||
192.168.2.13
|
||||
192.168.2.14
|
||||
192.168.2.16
|
||||
192.168.2.17
|
||||
192.168.2.18
|
||||
192.168.2.19
|
||||
192.168.2.20
|
||||
192.168.2.22
|
||||
192.168.2.23
|
||||
192.168.2.24
|
||||
@@ -1,21 +0,0 @@
|
||||
[servers]
|
||||
ansible-server.home.mikemcfetridge.com
|
||||
arrsserver.home.mikemcfetridge.com
|
||||
beszel-server.home.mikemcfetridge.com
|
||||
checkmk.home.mikemcfetridge.com
|
||||
dockerapps.home.mikemcfetridge.com
|
||||
gitea-server.home.mikemcfetridge.com
|
||||
glance-server.home.mikemcfetridge.com
|
||||
immich-wallabag.home.mikemcfetridge.com
|
||||
linkwarden.home.mikemcfetridge.com
|
||||
netbird-1.home.mikemcfetridge.com
|
||||
netbird-2.home.mikemcfetridge.com
|
||||
npm-server.home.mikemcfetridge.com
|
||||
pihole-server.home.mikemcfetridge.com
|
||||
pve.home.mikemcfetridge.com
|
||||
pve2.home.mikemcfetridge.com
|
||||
retrogaming-server.home.mikemcfetridge.com
|
||||
searxng-server.home.mikemcfetridge.com
|
||||
technitiumdns-server.home.mikemcfetridge.com
|
||||
vaultwarden-server.home.mikemcfetridge.com
|
||||
wireguard-server.home.mikemcfetridge.com
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
- name: "Manage Checkmk rules"
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
vars_files:
|
||||
- secrets.yaml
|
||||
vars:
|
||||
server_url: "http://192.168.2.4:5000"
|
||||
site: "cmk"
|
||||
|
||||
tasks:
|
||||
- name: Get a rule with a particular rule id
|
||||
ansible.builtin.debug:
|
||||
msg: "Rule: {{ extensions | to_nice_yaml }}"
|
||||
vars:
|
||||
extensions: "{{
|
||||
lookup('checkmk.general.rule',
|
||||
rule_id='checkmk-rule-id',
|
||||
server_url=server_url,
|
||||
site=site,
|
||||
automation_user=automation_user,
|
||||
automation_secret=automation_secret,
|
||||
validate_certs=False
|
||||
)
|
||||
}}"
|
||||
@@ -1,286 +0,0 @@
|
||||
---
|
||||
- name: "Manage Checkmk hosts"
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
vars_files:
|
||||
- secrets.yaml
|
||||
vars:
|
||||
server_url: "http://192.168.2.4:5000"
|
||||
site: "cmk"
|
||||
|
||||
tasks:
|
||||
|
||||
- name: "Create host - PVE01"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "pve01"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.1"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - PVE02"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "pve02"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.2"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - beszel"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "Beszel"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.3"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - Checkmk"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "Checkmk"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.4"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - komodo"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "komodo"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.5"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - Datacenter"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "Datacenter"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.6"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - DockerApps"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "DockerApps"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.7"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
|
||||
- name: "Create host - PIHOLE"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "PiHole"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.8"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - TECHNITIUMDNS"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "TechnitiumDNS"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.9"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - Wireguard"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "Wireguard"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.10"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - Ansible"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "Ansible"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.11"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - LinkWarden"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "LinkWarden"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.12"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - Immich"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "Immich"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.13"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - RetroGaming"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "RetroGaming"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.14"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - MediaServer"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "MediaServer"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.16"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - NetBird1"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "NetBird1"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.17"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - NetBird2"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "NetBird2"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.18"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - VaultWarden"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "VaultWarden"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.19"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - Glance"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "Glance"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.20"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - NPM"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "Npm"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.22"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - Searxng"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "Searxng"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.23"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Create host - Gitea"
|
||||
checkmk.general.host:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
name: "Gitea"
|
||||
attributes:
|
||||
ipaddress: "192.168.2.24"
|
||||
folder: "/"
|
||||
state: "present"
|
||||
|
||||
- name: "Start activation on a specific site"
|
||||
checkmk.general.activation:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
force_foreign_changes: 'true'
|
||||
sites:
|
||||
- "{{ site }}"
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
- name: "Manage Checkmk rules"
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
vars_files:
|
||||
- secrets.yaml
|
||||
vars:
|
||||
server_url: "checkmk.home.arpa"
|
||||
site: "cmk"
|
||||
|
||||
tasks:
|
||||
- name: Create DNS Check Rule
|
||||
checkmk.general.rule:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
ruleset: "active_checks:dns"
|
||||
rule:
|
||||
properties: {
|
||||
"comment": "Ansible managed",
|
||||
"description": "DNS DNS Monitoring",
|
||||
"disabled": false,
|
||||
}
|
||||
conditions: {
|
||||
"host_label_groups": [],
|
||||
"host_name": {
|
||||
"match_on": [
|
||||
"your-dns-container-host"
|
||||
],
|
||||
"operator": "one_of"
|
||||
},
|
||||
"host_tags": [],
|
||||
"service_label_groups": []
|
||||
}
|
||||
"value_raw": {
|
||||
"hostname": "hostname-to-query",
|
||||
"server": "dns-server-ip",
|
||||
"expected_addresses_list": [
|
||||
"expected-ip-address"
|
||||
]
|
||||
}
|
||||
location:
|
||||
folder: "/"
|
||||
position: "top"
|
||||
state: "present"
|
||||
|
||||
- name: Create NVME Temperature override rule
|
||||
checkmk.general.rule:
|
||||
server_url: "{{ server_url }}"
|
||||
site: "{{ site }}"
|
||||
automation_user: "{{ automation_user }}"
|
||||
automation_secret: "{{ automation_secret }}"
|
||||
ruleset: "checkgroup_parameters:temperature"
|
||||
rule:
|
||||
conditions:
|
||||
host_label_groups: []
|
||||
host_tags: []
|
||||
service_description:
|
||||
match_on:
|
||||
- "DRIVE MODEL NAME*"
|
||||
operator: "one_of"
|
||||
service_label_groups: []
|
||||
location:
|
||||
folder: "/"
|
||||
position: "top"
|
||||
properties:
|
||||
description: "NVME Temperature override"
|
||||
disabled: false
|
||||
value_raw: "{'levels': (60.0, 80.0)}"
|
||||
state: present
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
automation_user: "ansible"
|
||||
automation_secret: "Px40If4VUvvA35"
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
- name: Enable Docker TLS
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
certs_path: "<< certs_path >>"
|
||||
|
||||
tasks:
|
||||
- name: Check if docker certs are existing
|
||||
ansible.builtin.stat:
|
||||
path: {{ certs_path }}
|
||||
register: certs_dir
|
||||
|
||||
- name: Fail if docker certs are not existing
|
||||
ansible.builtin.fail:
|
||||
msg: "Docker certificates are not existing in /root/docker-certs."
|
||||
when: not certs_dir.stat.exists
|
||||
|
||||
- name: Get machine's primary internal ip address from eth0 interface
|
||||
ansible.builtin.setup:
|
||||
register: ip_address
|
||||
|
||||
- name: Set machine's primary internal ip address
|
||||
ansible.builtin.set_fact:
|
||||
ip_address: {{ ip_address.ansible_facts.ansible_default_ipv4.address }}
|
||||
|
||||
- name: Check if ip_address is a valid ip address
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ip_address is match("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$")
|
||||
fail_msg: "ip_address is not a valid ip address."
|
||||
success_msg: "ip_address is a valid ip address."
|
||||
|
||||
- name: Change docker daemon to use certs
|
||||
ansible.builtin.lineinfile:
|
||||
path: /lib/systemd/system/docker.service
|
||||
line: >
|
||||
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
|
||||
-H tcp://{{ ip_address }}:2376 --tlsverify --tlscacert={{ certs_path }}/ca.pem
|
||||
--tlscert={{ certs_path }}/server-cert.pem --tlskey={{ certs_path }}/server-key.pem
|
||||
regexp: '^ExecStart='
|
||||
state: present
|
||||
|
||||
- name: Reload systemd daemon
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart docker daemon
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
state: restarted
|
||||
enabled: true
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"slug": "docker-certs-enable",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Enable Docker TLS",
|
||||
"description": "Enables TLS on the Docker daemon using existing certificates. Configures Docker for secure remote API access over an encrypted connection.",
|
||||
"tags": [],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "docker"
|
||||
},
|
||||
"draft": true,
|
||||
"version": {
|
||||
"name": "1.0.0",
|
||||
"source_dep_name": "manual/docker-certs-enable"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Certificate Configuration",
|
||||
"name": "certificates",
|
||||
"items": [
|
||||
{
|
||||
"name": "certs_path",
|
||||
"type": "str",
|
||||
"title": "Certs Path",
|
||||
"required": false,
|
||||
"default": "/root/docker-certs",
|
||||
"description": "Path where certificates are stored",
|
||||
"config": {
|
||||
"placeholder": "/root/docker-certs"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
---
|
||||
- name: Generate Docker TLS certificates
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
certs_path: "<< certs_path >>"
|
||||
cert_validity_days: << cert_validity_days >>
|
||||
cn_domain: "<< cn_domain >>"
|
||||
|
||||
tasks:
|
||||
- name: Check if docker certs are existing
|
||||
ansible.builtin.stat:
|
||||
path: {{ certs_path }}
|
||||
register: certs_dir
|
||||
|
||||
- name: Create docker certs directory (if needed)
|
||||
ansible.builtin.file:
|
||||
path: {{ certs_path }}
|
||||
state: directory
|
||||
mode: '0700'
|
||||
when: not certs_dir.stat.exists
|
||||
|
||||
- name: Check if docker certs directory is empty
|
||||
ansible.builtin.command: ls -A {{ certs_path }}
|
||||
register: certs_list
|
||||
when: certs_dir.stat.exists
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Fail if docker certs already exist
|
||||
ansible.builtin.fail:
|
||||
msg: "Docker certificates already exist in /root/docker-certs."
|
||||
when: certs_list.stdout | default('') != ''
|
||||
|
||||
- name: Get machine's primary internal ip address from eth0 interface
|
||||
ansible.builtin.setup:
|
||||
register: ip_address
|
||||
|
||||
- name: Set machine's primary internal ip address
|
||||
ansible.builtin.set_fact:
|
||||
ip_address: {{ ip_address.ansible_facts.ansible_default_ipv4.address }}
|
||||
|
||||
- name: Check if ip_address is a valid ip address
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ip_address is match("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$")
|
||||
fail_msg: "ip_address is not a valid ip address."
|
||||
success_msg: "ip_address is a valid ip address."
|
||||
|
||||
- name: Generate CA private key
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
openssl genrsa -out {{ certs_path }}/ca-key.pem 4096
|
||||
args:
|
||||
creates: {{ certs_path }}/ca-key.pem
|
||||
|
||||
- name: Generate CA certificate
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
openssl req -sha256 -new -x509
|
||||
-subj "/CN={{ cn_domain }}"
|
||||
-days {{ cert_validity_days }}
|
||||
-key {{ certs_path }}/ca-key.pem
|
||||
-out {{ certs_path }}/ca.pem
|
||||
args:
|
||||
creates: {{ certs_path }}/ca.pem
|
||||
|
||||
- name: Generate server private key
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
openssl genrsa -out {{ certs_path }}/server-key.pem 4096
|
||||
creates: {{ certs_path }}/server-key.pem
|
||||
|
||||
- name: Generate server certificate signing request
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
openssl req -sha256 -new
|
||||
-subj "/CN={{ inventory_hostname }}"
|
||||
-key {{ certs_path }}/server-key.pem
|
||||
-out {{ certs_path }}/server.csr
|
||||
creates: {{ certs_path }}/server.csr
|
||||
|
||||
- name: Generate server certificate extension file
|
||||
ansible.builtin.shell: |
|
||||
echo "subjectAltName = DNS:{{ inventory_hostname }},IP:{{ ip_address }},IP:127.0.0.1" >> {{ certs_path }}/extfile.cnf
|
||||
echo "extendedKeyUsage = serverAuth" >> {{ certs_path }}/extfile.cnf
|
||||
args:
|
||||
creates: {{ certs_path }}/extfile.cnf
|
||||
|
||||
- name: Generate server certificate
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
openssl x509 -req -days {{ cert_validity_days }} -sha256
|
||||
-in {{ certs_path }}/server.csr
|
||||
-CA {{ certs_path }}/ca.pem
|
||||
-CAkey {{ certs_path }}/ca-key.pem
|
||||
-CAcreateserial -out {{ certs_path }}/server-cert.pem
|
||||
-extfile {{ certs_path }}/extfile.cnf
|
||||
creates: {{ certs_path }}/server-cert.pem
|
||||
|
||||
- name: Generate client private key
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
openssl genrsa -out {{ certs_path }}/key.pem 4096
|
||||
creates: {{ certs_path }}/key.pem
|
||||
|
||||
- name: Generate client certificate signing request
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
openssl req -sha256 -new
|
||||
-subj "/CN=client"
|
||||
-key {{ certs_path }}/key.pem
|
||||
-out {{ certs_path }}/client.csr
|
||||
creates: {{ certs_path }}/client.csr
|
||||
|
||||
- name: Generate client certificate extension file
|
||||
ansible.builtin.shell: |
|
||||
echo "extendedKeyUsage = clientAuth" >> {{ certs_path }}/client-extfile.cnf
|
||||
args:
|
||||
creates: {{ certs_path }}/client-extfile.cnf
|
||||
|
||||
- name: Generate client certificate
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
openssl x509 -req -days {{ cert_validity_days }}
|
||||
-sha256 -in {{ certs_path }}/client.csr
|
||||
-CA {{ certs_path }}/ca.pem
|
||||
-CAkey {{ certs_path }}/ca-key.pem
|
||||
-CAcreateserial -out {{ certs_path }}/cert.pem
|
||||
-extfile {{ certs_path }}/client-extfile.cnf
|
||||
creates: {{ certs_path }}/cert.pem
|
||||
|
||||
- name: Remove client certificate signing request
|
||||
ansible.builtin.file:
|
||||
path: {{ certs_path }}/server.csr
|
||||
state: absent
|
||||
|
||||
- name: Remove client certificate signing request
|
||||
ansible.builtin.file:
|
||||
path: {{ certs_path }}/client.csr
|
||||
state: absent
|
||||
|
||||
- name: Remove server certificate extension file
|
||||
ansible.builtin.file:
|
||||
path: {{ certs_path }}/extfile.cnf
|
||||
state: absent
|
||||
|
||||
- name: Remove client certificate extension file
|
||||
ansible.builtin.file:
|
||||
path: {{ certs_path }}/client-extfile.cnf
|
||||
state: absent
|
||||
|
||||
- name: Set permissions for docker certs
|
||||
ansible.builtin.file:
|
||||
path: {{ certs_path }}
|
||||
mode: '0700'
|
||||
recurse: true
|
||||
follow: true
|
||||
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"slug": "docker-certs",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Generate Docker TLS Certificates",
|
||||
"description": "Generates TLS certificates for the Docker daemon, including CA, server, and client certificates. Used to secure Docker remote API access.",
|
||||
"tags": [],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "docker"
|
||||
},
|
||||
"draft": true,
|
||||
"version": {
|
||||
"name": "1.0.0",
|
||||
"source_dep_name": "manual/docker-certs"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Certificate Configuration",
|
||||
"name": "certificates",
|
||||
"items": [
|
||||
{
|
||||
"name": "certs_path",
|
||||
"type": "str",
|
||||
"title": "Certs Path",
|
||||
"required": false,
|
||||
"default": "/root/docker-certs",
|
||||
"description": "Path where certificates will be stored",
|
||||
"config": {
|
||||
"placeholder": "/root/docker-certs"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cert_validity_days",
|
||||
"type": "int",
|
||||
"title": "Validity Days",
|
||||
"required": false,
|
||||
"default": 3630,
|
||||
"description": "Certificate validity period in days",
|
||||
"config": {
|
||||
"slider": true,
|
||||
"min": 30,
|
||||
"max": 3650,
|
||||
"step": 30,
|
||||
"placeholder": "3650",
|
||||
"unit": "days"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cn_domain",
|
||||
"type": "str",
|
||||
"title": "CA Common Name",
|
||||
"required": false,
|
||||
"description": "Common Name (CN) for the CA certificate",
|
||||
"config": {
|
||||
"placeholder": "home.arpa.tld"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
- name: Docker prune safe
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Prune Docker resources with safe defaults
|
||||
community.docker.docker_prune:
|
||||
containers: << prune_stopped_containers | default(false) >>
|
||||
images: true
|
||||
images_filters:
|
||||
dangling: << not (prune_include_unused_tagged_images | default(false)) >>
|
||||
networks: << prune_unused_networks | default(false) >>
|
||||
volumes: << prune_unused_volumes | default(false) >>
|
||||
builder_cache: << prune_builder_cache | default(false) >>
|
||||
builder_cache_keep_storage: "<< prune_keep_storage_mb | default(1024) >>M"
|
||||
timeout: 300
|
||||
@@ -1,2 +0,0 @@
|
||||
collections:
|
||||
- name: community.docker
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"slug": "docker-cleanup",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Docker Cleanup",
|
||||
"description": "Safely prunes Docker resources on target hosts. Only removes resources that are explicitly enabled in the configuration, keeping a conservative default.",
|
||||
"tags": [
|
||||
"docker",
|
||||
"cleanup",
|
||||
"prune",
|
||||
"maintenance"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "mdi",
|
||||
"id": "broom",
|
||||
"color": "fuchsia"
|
||||
},
|
||||
"draft": false,
|
||||
"version": {
|
||||
"name": "1",
|
||||
"source_dep_name": "manual/docker-cleanup"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Prune Options",
|
||||
"name": "prune",
|
||||
"items": [
|
||||
{
|
||||
"name": "prune_include_unused_tagged_images",
|
||||
"type": "bool",
|
||||
"title": "Tagged Images",
|
||||
"required": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "prune_stopped_containers",
|
||||
"type": "bool",
|
||||
"title": "Prune Stopped Containers",
|
||||
"required": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "prune_unused_volumes",
|
||||
"type": "bool",
|
||||
"title": "Prune Unused Volumes",
|
||||
"required": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "prune_unused_networks",
|
||||
"type": "bool",
|
||||
"title": "Prune Unused Networks",
|
||||
"required": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "prune_builder_cache",
|
||||
"type": "bool",
|
||||
"title": "Prune Builder Cache",
|
||||
"required": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "prune_keep_storage_mb",
|
||||
"type": "int",
|
||||
"title": "Keep Storage",
|
||||
"required": false,
|
||||
"default": 1024,
|
||||
"description": "Minimum amount of cache/storage to keep when pruning (where supported)",
|
||||
"config": {
|
||||
"slider": true,
|
||||
"min": 256,
|
||||
"max": 102400,
|
||||
"step": 256,
|
||||
"placeholder": "1024",
|
||||
"unit": "MB"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
- name: Docker disk usage report
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
report_root: "/opt/docker-reports"
|
||||
report_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
|
||||
report_file_name: "docker_disk_usage_<< inventory_hostname >>_<< report_timestamp >>.txt"
|
||||
|
||||
tasks:
|
||||
- name: Ensure report root exists
|
||||
ansible.builtin.file:
|
||||
path: "<< report_root >>"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
|
||||
- name: Collect docker system df summary
|
||||
ansible.builtin.command: docker system df
|
||||
register: docker_df_summary
|
||||
changed_when: false
|
||||
|
||||
- name: Collect docker system df verbose report
|
||||
ansible.builtin.command: docker system df -v
|
||||
register: docker_df_verbose
|
||||
changed_when: false
|
||||
when: include_verbose_report | default(false)
|
||||
|
||||
- name: Build report body
|
||||
ansible.builtin.set_fact:
|
||||
docker_disk_usage_report_body: |
|
||||
Host: << inventory_hostname >>
|
||||
Timestamp: {{ lookup('pipe', 'date -u +%Y-%m-%dT%H:%M:%SZ') }}
|
||||
|
||||
Docker system df:
|
||||
<< docker_df_summary.stdout >>
|
||||
<%- if include_verbose_report | default(false) %>
|
||||
|
||||
Docker system df -v:
|
||||
<< docker_df_verbose.stdout >>
|
||||
<%- endif %>
|
||||
|
||||
- name: Save local disk usage report
|
||||
ansible.builtin.copy:
|
||||
dest: "<< report_root >>/<< report_file_name >>"
|
||||
content: "<< docker_disk_usage_report_body >>"
|
||||
mode: "0640"
|
||||
|
||||
- name: Build Discord report content
|
||||
ansible.builtin.set_fact:
|
||||
discord_disk_report_content: |
|
||||
<< discord_message_prefix | default('Docker Disk Usage Report') >>
|
||||
```text
|
||||
<< docker_disk_usage_report_body | truncate(1700, true, '...') >>
|
||||
```
|
||||
when:
|
||||
- discord_enabled | default(false)
|
||||
- (discord_webhook_url | default('')) | length > 0
|
||||
|
||||
- name: Send disk usage report to Discord webhook
|
||||
ansible.builtin.uri:
|
||||
url: "<< discord_webhook_url | default('') >>"
|
||||
method: POST
|
||||
body_format: json
|
||||
body:
|
||||
username: "<< discord_username | default('Docker Reporter') >>"
|
||||
content: "<< discord_disk_report_content >>"
|
||||
status_code: 204
|
||||
when:
|
||||
- discord_enabled | default(false)
|
||||
- (discord_webhook_url | default('')) | length > 0
|
||||
@@ -1,148 +0,0 @@
|
||||
{
|
||||
"slug": "docker-disk-usage-report",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Docker Disk Usage Report",
|
||||
"description": "Generates a Docker disk usage report on target hosts and optionally sends a summary to Discord via webhook.",
|
||||
"tags": [
|
||||
"docker",
|
||||
"report",
|
||||
"disk",
|
||||
"monitoring"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "mdi",
|
||||
"id": "chart-box",
|
||||
"color": "blue"
|
||||
},
|
||||
"draft": true,
|
||||
"version": {
|
||||
"name": "1",
|
||||
"source_dep_name": "manual/docker-disk-usage-report"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Report",
|
||||
"name": "report",
|
||||
"items": [
|
||||
{
|
||||
"name": "include_verbose_report",
|
||||
"type": "bool",
|
||||
"title": "Verbose Report",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Include docker system df -v output in local report"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Discord Webhook",
|
||||
"name": "discord",
|
||||
"items": [
|
||||
{
|
||||
"name": "discord_enabled",
|
||||
"type": "bool",
|
||||
"title": "Discord",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Send a short report to Discord webhook"
|
||||
},
|
||||
{
|
||||
"name": "discord_webhook_url",
|
||||
"type": "str",
|
||||
"title": "Discord Webhook URL",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "discord_username",
|
||||
"type": "str",
|
||||
"title": "Discord Username",
|
||||
"required": false,
|
||||
"default": "Docker Reporter",
|
||||
"description": "Sender name for webhook message",
|
||||
"config": {
|
||||
"placeholder": "Docker Reporter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "discord_message_prefix",
|
||||
"type": "str",
|
||||
"title": "Discord Message Prefix",
|
||||
"required": false,
|
||||
"default": "Docker Disk Usage Report",
|
||||
"description": "Prefix line sent before report content",
|
||||
"config": {
|
||||
"placeholder": "Docker Disk Usage Report"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Internal",
|
||||
"name": "internal",
|
||||
"items": [
|
||||
{
|
||||
"name": "docker_df_verbose",
|
||||
"type": "str",
|
||||
"title": "Docker DF Verbose",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "report_file_name",
|
||||
"type": "str",
|
||||
"title": "Report File Name",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "report_root",
|
||||
"type": "str",
|
||||
"title": "Report Root",
|
||||
"required": false,
|
||||
"default": "/reports",
|
||||
"config": {
|
||||
"placeholder": "/reports"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "docker_disk_usage_report_body",
|
||||
"type": "str",
|
||||
"title": "Report Body",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "discord_disk_report_content",
|
||||
"type": "str",
|
||||
"title": "Discord Report",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "inventory_hostname",
|
||||
"type": "str",
|
||||
"title": "Inventory Hostname",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "report_timestamp",
|
||||
"type": "str",
|
||||
"title": "Report Timestamp",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "docker_df_summary",
|
||||
"type": "str",
|
||||
"title": "Docker DF Summary",
|
||||
"required": false,
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
- name: Docker healthcheck audit
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Collect Docker containers
|
||||
community.docker.docker_host_info:
|
||||
containers: true
|
||||
register: docker_host_info
|
||||
|
||||
- name: Build list of containers missing healthcheck
|
||||
ansible.builtin.set_fact:
|
||||
containers_missing_healthcheck: "{{ (containers_missing_healthcheck | default([])) + [item.Names[0] | default(item.Id)] }}"
|
||||
when:
|
||||
- item.Config is defined
|
||||
- item.Config.Healthcheck is not defined
|
||||
loop: "{{ docker_host_info.containers | default([]) }}"
|
||||
|
||||
- name: Ensure missing-healthcheck list exists
|
||||
ansible.builtin.set_fact:
|
||||
containers_missing_healthcheck: []
|
||||
when: containers_missing_healthcheck is not defined
|
||||
|
||||
- name: Build audit report
|
||||
ansible.builtin.set_fact:
|
||||
docker_healthcheck_audit_report:
|
||||
checked_containers: "{{ docker_host_info.containers | default([]) | length }}"
|
||||
missing_healthcheck_count: "{{ containers_missing_healthcheck | length }}"
|
||||
missing_healthcheck_containers: "{{ containers_missing_healthcheck }}"
|
||||
|
||||
- name: Save healthcheck audit report
|
||||
ansible.builtin.copy:
|
||||
dest: "<< audit_report_path | default('/tmp/docker-healthcheck-audit.json') >>"
|
||||
content: "{{ docker_healthcheck_audit_report | to_nice_json }}"
|
||||
mode: "0640"
|
||||
when: audit_save_report | default(true)
|
||||
|
||||
- name: Print audit summary
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
Checked {{ docker_healthcheck_audit_report.checked_containers }} containers,
|
||||
found {{ docker_healthcheck_audit_report.missing_healthcheck_count }} without healthcheck.
|
||||
|
||||
- name: Fail when missing healthchecks are found
|
||||
ansible.builtin.fail:
|
||||
msg: "Containers missing healthcheck: {{ containers_missing_healthcheck | join(', ') }}"
|
||||
when:
|
||||
- audit_fail_on_missing | default(false)
|
||||
- containers_missing_healthcheck | length > 0
|
||||
@@ -1,2 +0,0 @@
|
||||
collections:
|
||||
- name: community.docker
|
||||
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"slug": "docker-healthcheck-audit",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Docker Healthcheck Audit",
|
||||
"description": "Audits Docker containers for missing healthchecks and optionally fails the playbook when violations are found. Can save a JSON audit report to the target host.",
|
||||
"tags": [
|
||||
"docker",
|
||||
"audit",
|
||||
"healthcheck",
|
||||
"compliance"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "mdi",
|
||||
"id": "stethoscope",
|
||||
"color": "blue"
|
||||
},
|
||||
"draft": false,
|
||||
"version": {
|
||||
"name": "1",
|
||||
"source_dep_name": "manual/docker-healthcheck-audit"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Audit",
|
||||
"name": "audit",
|
||||
"items": [
|
||||
{
|
||||
"name": "audit_fail_on_missing",
|
||||
"type": "bool",
|
||||
"title": "Fail Missing Checks",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Fail the play if one or more containers are missing a healthcheck"
|
||||
},
|
||||
{
|
||||
"name": "audit_save_report",
|
||||
"type": "bool",
|
||||
"title": "Save JSON Report",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Save audit report to a JSON file on the target host"
|
||||
},
|
||||
{
|
||||
"name": "audit_report_path",
|
||||
"type": "str",
|
||||
"title": "Audit Report Path",
|
||||
"required": false,
|
||||
"default": "/tmp/docker-healthcheck-audit.json",
|
||||
"description": "Path to store the generated JSON report",
|
||||
"config": {
|
||||
"placeholder": "/tmp/docker-healthcheck-audit.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Internal",
|
||||
"name": "internal",
|
||||
"items": [
|
||||
{
|
||||
"name": "containers_missing_healthcheck",
|
||||
"type": "str",
|
||||
"title": "Containers Missing Healthcheck",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "docker_healthcheck_audit_report",
|
||||
"type": "str",
|
||||
"title": "Healthcheck Audit Report",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "docker_host_info",
|
||||
"type": "str",
|
||||
"title": "Docker Host Info",
|
||||
"required": false,
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
- name: Install Docker on Ubuntu
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
vars:
|
||||
docker_apt_arch_map:
|
||||
x86_64: amd64
|
||||
aarch64: arm64
|
||||
armv7l: armhf
|
||||
ppc64le: ppc64el
|
||||
s390x: s390x
|
||||
docker_apt_arch: "{{ docker_apt_arch_map[ansible_architecture] | default('amd64') }}"
|
||||
|
||||
tasks:
|
||||
- name: Remove conflicting packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- docker.io
|
||||
- docker-compose
|
||||
- docker-compose-v2
|
||||
- docker-doc
|
||||
- podman-docker
|
||||
- containerd
|
||||
- runc
|
||||
state: absent
|
||||
|
||||
- name: Install prerequisite packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- curl
|
||||
update_cache: true
|
||||
state: present
|
||||
|
||||
- name: Create apt keyrings directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Download Docker GPG key
|
||||
ansible.builtin.get_url:
|
||||
url: https://download.docker.com/linux/ubuntu/gpg
|
||||
dest: /etc/apt/keyrings/docker.asc
|
||||
mode: "0644"
|
||||
force: true
|
||||
|
||||
- name: Add Docker apt repository list file
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/sources.list.d/docker.list
|
||||
mode: "0644"
|
||||
content: |
|
||||
deb [arch={{ docker_apt_arch }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable
|
||||
|
||||
- name: Install Docker Engine packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
update_cache: true
|
||||
state: present
|
||||
|
||||
- name: Ensure Docker service is enabled and started
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
<%- if add_user_to_docker == true %>
|
||||
- name: Add user to docker group
|
||||
ansible.builtin.user:
|
||||
name: "<< user >>"
|
||||
groups: docker
|
||||
append: true
|
||||
<%- endif %>
|
||||
|
||||
- name: Verify Docker CLI is available
|
||||
ansible.builtin.command: docker --version
|
||||
changed_when: false
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"slug": "docker-install-ubuntu",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Install Docker on Ubuntu",
|
||||
"description": "Installs Docker Engine on Ubuntu systems using the official Docker APT repository.",
|
||||
"tags": [],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "docker",
|
||||
"color": "blue"
|
||||
},
|
||||
"draft": false,
|
||||
"version": {
|
||||
"name": "1",
|
||||
"source_dep_name": "manual/docker-install-ubuntu"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "User Access",
|
||||
"name": "postinstall",
|
||||
"items": [
|
||||
{
|
||||
"name": "add_user_to_docker",
|
||||
"type": "bool",
|
||||
"title": "Docker Group",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Add a user to the docker group"
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"type": "str",
|
||||
"title": "User",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Username to add to docker group",
|
||||
"needs": [
|
||||
"add_user_to_docker=true"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
---
|
||||
- name: Docker mysql backup
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
backup_root: "/opt/docker-db-backups/mysql"
|
||||
backup_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
|
||||
backup_file_name: "backup_mysql_<< container_name >>_<< backup_timestamp >>.sql.gz"
|
||||
backup_file_path: "<< backup_root >>/<< backup_file_name >>"
|
||||
|
||||
tasks:
|
||||
- name: Ensure backup root exists
|
||||
ansible.builtin.file:
|
||||
path: "<< backup_root >>"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
|
||||
- name: Get container information
|
||||
community.docker.docker_container_info:
|
||||
name: "<< container_name >>"
|
||||
register: container_info
|
||||
|
||||
- name: Fail if container does not exist
|
||||
ansible.builtin.fail:
|
||||
msg: "Container '<< container_name >>' does not exist"
|
||||
when: not container_info.exists
|
||||
|
||||
- name: Run backup and validation
|
||||
block:
|
||||
- name: Dump all databases with password
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
docker exec -e MYSQL_PWD=<< mysql_password | quote >> "<< container_name >>" \
|
||||
mysqldump --single-transaction --routines --events --triggers \
|
||||
--all-databases -u "<< mysql_user | default('root') >>" \
|
||||
| gzip > "<< backup_file_path >>"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
when:
|
||||
- mysql_database | default('all') == 'all'
|
||||
- (mysql_password | default('')) | length > 0
|
||||
|
||||
- name: Dump all databases without password
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
docker exec "<< container_name >>" \
|
||||
mysqldump --single-transaction --routines --events --triggers \
|
||||
--all-databases -u "<< mysql_user | default('root') >>" \
|
||||
| gzip > "<< backup_file_path >>"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
when:
|
||||
- mysql_database | default('all') == 'all'
|
||||
- (mysql_password | default('')) | length == 0
|
||||
|
||||
- name: Dump selected database with password
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
docker exec -e MYSQL_PWD=<< mysql_password | quote >> "<< container_name >>" \
|
||||
mysqldump --single-transaction --routines --events --triggers \
|
||||
-u "<< mysql_user | default('root') >>" "<< mysql_database >>" \
|
||||
| gzip > "<< backup_file_path >>"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
when:
|
||||
- mysql_database | default('all') != 'all'
|
||||
- (mysql_password | default('')) | length > 0
|
||||
|
||||
- name: Dump selected database without password
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
docker exec "<< container_name >>" \
|
||||
mysqldump --single-transaction --routines --events --triggers \
|
||||
-u "<< mysql_user | default('root') >>" "<< mysql_database >>" \
|
||||
| gzip > "<< backup_file_path >>"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
when:
|
||||
- mysql_database | default('all') != 'all'
|
||||
- (mysql_password | default('')) | length == 0
|
||||
|
||||
- name: Validate backup file exists
|
||||
ansible.builtin.stat:
|
||||
path: "<< backup_file_path >>"
|
||||
register: backup_archive_stat
|
||||
|
||||
- name: Fail if backup file is missing or empty
|
||||
ansible.builtin.fail:
|
||||
msg: "Backup file missing or empty: << backup_file_path >>"
|
||||
when:
|
||||
- not (backup_archive_stat.stat.exists | default(false))
|
||||
or (backup_archive_stat.stat.size | default(0) | int) == 0
|
||||
|
||||
- name: Validate gzip archive integrity
|
||||
ansible.builtin.command: gzip -t "<< backup_file_path >>"
|
||||
changed_when: false
|
||||
|
||||
- name: Validate mysql dump signature
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
gzip -dc "<< backup_file_path >>" \
|
||||
| head -n 50 \
|
||||
| grep -Eq 'MySQL dump'
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
|
||||
- name: Remove old mysql backup archives
|
||||
ansible.builtin.find:
|
||||
paths: "<< backup_root >>"
|
||||
patterns: "backup_mysql_<< container_name >>_*.sql.gz"
|
||||
age: "<< backup_retention_days | default(14) >>d"
|
||||
recurse: true
|
||||
register: old_backups
|
||||
|
||||
- name: Delete old mysql backup archives
|
||||
ansible.builtin.file:
|
||||
path: "<< item.path >>"
|
||||
state: absent
|
||||
loop: "<< old_backups.files >>"
|
||||
|
||||
- name: Mark backup status success
|
||||
ansible.builtin.set_fact:
|
||||
backup_status: "success"
|
||||
backup_status_message: "Backup and validation succeeded"
|
||||
rescue:
|
||||
- name: Mark backup status failed
|
||||
ansible.builtin.set_fact:
|
||||
backup_status: "failed"
|
||||
backup_status_message: "<< ansible_failed_result.msg | default('Backup or validation failed') >>"
|
||||
always:
|
||||
- name: Send mysql backup status to Discord
|
||||
ansible.builtin.uri:
|
||||
url: "<< discord_webhook | default('') >>"
|
||||
method: POST
|
||||
body_format: json
|
||||
body:
|
||||
content: |
|
||||
MySQL Backup << backup_status | default('failed') | upper >>
|
||||
Host: << inventory_hostname >>
|
||||
Container: << container_name >>
|
||||
File: << backup_file_path >>
|
||||
Message: << backup_status_message | default('n/a') >>
|
||||
status_code: 204
|
||||
when:
|
||||
- send_discord_notification | default(false)
|
||||
- (discord_webhook | default('')) | length > 0
|
||||
|
||||
- name: Fail play if backup failed
|
||||
ansible.builtin.fail:
|
||||
msg: "<< backup_status_message | default('Backup failed') >>"
|
||||
when: backup_status | default('failed') == 'failed'
|
||||
@@ -1,2 +0,0 @@
|
||||
collections:
|
||||
- name: community.docker
|
||||
@@ -1,186 +0,0 @@
|
||||
{
|
||||
"slug": "docker-mysql-backup",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Docker MySQL Backup",
|
||||
"description": "Connects to a named MySQL container and creates compressed SQL dumps with configurable retention. Supports optional Discord notifications on success or failure.",
|
||||
"tags": [
|
||||
"docker",
|
||||
"backup",
|
||||
"mysql",
|
||||
"database"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "mysql",
|
||||
"color": "emerald"
|
||||
},
|
||||
"draft": true,
|
||||
"version": {
|
||||
"name": "1",
|
||||
"source_dep_name": "manual/docker-mysql-backup"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Backup",
|
||||
"name": "backup",
|
||||
"items": [
|
||||
{
|
||||
"name": "container_name",
|
||||
"type": "str",
|
||||
"title": "Container Name",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "mysql_user",
|
||||
"type": "str",
|
||||
"title": "MySQL User",
|
||||
"required": false,
|
||||
"default": "root",
|
||||
"config": {
|
||||
"placeholder": "root"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mysql_password",
|
||||
"type": "str",
|
||||
"title": "MySQL Password",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "mysql_database",
|
||||
"type": "str",
|
||||
"title": "Database Name",
|
||||
"required": false,
|
||||
"default": "all",
|
||||
"description": "Database to dump, or 'all' for all databases",
|
||||
"config": {
|
||||
"placeholder": "all"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "backup_retention_days",
|
||||
"type": "int",
|
||||
"title": "Retention Days",
|
||||
"required": false,
|
||||
"default": 14,
|
||||
"description": "Delete backup archives older than this many days",
|
||||
"config": {
|
||||
"slider": true,
|
||||
"min": 1,
|
||||
"max": 365,
|
||||
"step": 1,
|
||||
"placeholder": "14",
|
||||
"unit": "days"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Notification",
|
||||
"name": "notification",
|
||||
"items": [
|
||||
{
|
||||
"name": "send_discord_notification",
|
||||
"type": "bool",
|
||||
"title": "Send Discord Notification",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Send success/failure status to Discord webhook"
|
||||
},
|
||||
{
|
||||
"name": "discord_webhook",
|
||||
"type": "secret",
|
||||
"title": "Discord Webhook",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Discord webhook URL for notifications"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Internal",
|
||||
"name": "internal",
|
||||
"items": [
|
||||
{
|
||||
"name": "backup_file_name",
|
||||
"type": "str",
|
||||
"title": "Backup File Name",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "backup_root",
|
||||
"type": "str",
|
||||
"title": "Backup Root",
|
||||
"required": false,
|
||||
"default": "/backups/mysql",
|
||||
"config": {
|
||||
"placeholder": "/backups/mysql"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ansible_failed_result",
|
||||
"type": "str",
|
||||
"title": "Failed Result",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "backup_timestamp",
|
||||
"type": "str",
|
||||
"title": "Backup Timestamp",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "old_backups",
|
||||
"type": "str",
|
||||
"title": "Old Backups",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "backup_status",
|
||||
"type": "str",
|
||||
"title": "Backup Status",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "inventory_hostname",
|
||||
"type": "str",
|
||||
"title": "Inventory Hostname",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "backup_file_path",
|
||||
"type": "str",
|
||||
"title": "Backup File Path",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "item",
|
||||
"type": "str",
|
||||
"title": "Loop Item",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "backup_status_message",
|
||||
"type": "str",
|
||||
"title": "Backup Status Message",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"config": {
|
||||
"textarea": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
- name: Docker orphan detection
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
report_root: "/opt/docker-reports"
|
||||
report_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
|
||||
report_file_name: "docker_orphan_detection_<< inventory_hostname >>_<< report_timestamp >>.txt"
|
||||
|
||||
tasks:
|
||||
- name: Ensure report root exists
|
||||
ansible.builtin.file:
|
||||
path: "<< report_root >>"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
|
||||
- name: List exited containers
|
||||
ansible.builtin.command: docker ps -a --filter status=exited --format "{{.Names}}"
|
||||
register: orphan_exited_containers
|
||||
changed_when: false
|
||||
|
||||
- name: List dead containers
|
||||
ansible.builtin.command: docker ps -a --filter status=dead --format "{{.Names}}"
|
||||
register: orphan_dead_containers
|
||||
changed_when: false
|
||||
|
||||
- name: List dangling images
|
||||
ansible.builtin.command: docker images -f dangling=true -q
|
||||
register: orphan_dangling_images
|
||||
changed_when: false
|
||||
|
||||
- name: List dangling volumes
|
||||
ansible.builtin.command: docker volume ls -qf dangling=true
|
||||
register: orphan_dangling_volumes
|
||||
changed_when: false
|
||||
|
||||
- name: List dangling networks
|
||||
ansible.builtin.command: docker network ls -qf dangling=true
|
||||
register: orphan_dangling_networks
|
||||
changed_when: false
|
||||
|
||||
- name: Build orphan report body
|
||||
ansible.builtin.set_fact:
|
||||
docker_orphan_report_body: |
|
||||
Host: << inventory_hostname >>
|
||||
Timestamp: {{ lookup('pipe', 'date -u +%Y-%m-%dT%H:%M:%SZ') }}
|
||||
|
||||
Exited containers (<< orphan_exited_containers.stdout_lines | length >>):
|
||||
<< (orphan_exited_containers.stdout_lines | default([])) | join('\n') if (orphan_exited_containers.stdout_lines | length > 0) else 'none' >>
|
||||
|
||||
Dead containers (<< orphan_dead_containers.stdout_lines | length >>):
|
||||
<< (orphan_dead_containers.stdout_lines | default([])) | join('\n') if (orphan_dead_containers.stdout_lines | length > 0) else 'none' >>
|
||||
|
||||
Dangling images (<< orphan_dangling_images.stdout_lines | length >>):
|
||||
<< (orphan_dangling_images.stdout_lines | default([])) | join('\n') if (orphan_dangling_images.stdout_lines | length > 0) else 'none' >>
|
||||
|
||||
Dangling volumes (<< orphan_dangling_volumes.stdout_lines | length >>):
|
||||
<< (orphan_dangling_volumes.stdout_lines | default([])) | join('\n') if (orphan_dangling_volumes.stdout_lines | length > 0) else 'none' >>
|
||||
|
||||
Dangling networks (<< orphan_dangling_networks.stdout_lines | length >>):
|
||||
<< (orphan_dangling_networks.stdout_lines | default([])) | join('\n') if (orphan_dangling_networks.stdout_lines | length > 0) else 'none' >>
|
||||
|
||||
- name: Save local orphan detection report
|
||||
ansible.builtin.copy:
|
||||
dest: "<< report_root >>/<< report_file_name >>"
|
||||
content: "<< docker_orphan_report_body >>"
|
||||
mode: "0640"
|
||||
|
||||
- name: Build Discord report content
|
||||
ansible.builtin.set_fact:
|
||||
discord_orphan_report_content: |
|
||||
<< discord_message_prefix | default('Docker Orphan Detection Report') >>
|
||||
```text
|
||||
<< docker_orphan_report_body | truncate(1700, true, '...') >>
|
||||
```
|
||||
when:
|
||||
- discord_enabled | default(false)
|
||||
- (discord_webhook_url | default('')) | length > 0
|
||||
|
||||
- name: Send orphan report to Discord webhook
|
||||
ansible.builtin.uri:
|
||||
url: "<< discord_webhook_url | default('') >>"
|
||||
method: POST
|
||||
body_format: json
|
||||
body:
|
||||
username: "<< discord_username | default('Docker Reporter') >>"
|
||||
content: "<< discord_orphan_report_content >>"
|
||||
status_code: 204
|
||||
when:
|
||||
- discord_enabled | default(false)
|
||||
- (discord_webhook_url | default('')) | length > 0
|
||||
@@ -1,155 +0,0 @@
|
||||
{
|
||||
"slug": "docker-orphan-detection",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Docker Orphan Detection",
|
||||
"description": "Detects orphaned Docker resources (dead/exited containers, dangling images, volumes, and networks) and optionally sends a report to Discord via webhook.",
|
||||
"tags": [
|
||||
"docker",
|
||||
"audit",
|
||||
"orphan",
|
||||
"report"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "mdi",
|
||||
"id": "magnify",
|
||||
"color": "fuchsia"
|
||||
},
|
||||
"draft": true,
|
||||
"version": {
|
||||
"name": "1",
|
||||
"source_dep_name": "manual/docker-orphan-detection"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Discord Webhook",
|
||||
"name": "discord",
|
||||
"items": [
|
||||
{
|
||||
"name": "discord_enabled",
|
||||
"type": "bool",
|
||||
"title": "Discord",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Send orphan detection summary to Discord webhook"
|
||||
},
|
||||
{
|
||||
"name": "discord_webhook_url",
|
||||
"type": "str",
|
||||
"title": "Discord Webhook URL",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "discord_username",
|
||||
"type": "str",
|
||||
"title": "Discord Username",
|
||||
"required": false,
|
||||
"default": "Docker Reporter",
|
||||
"description": "Sender name for webhook message",
|
||||
"config": {
|
||||
"placeholder": "Docker Reporter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "discord_message_prefix",
|
||||
"type": "str",
|
||||
"title": "Discord Message Prefix",
|
||||
"required": false,
|
||||
"default": "Docker Orphan Detection Report",
|
||||
"description": "Prefix line sent before report content",
|
||||
"config": {
|
||||
"placeholder": "Docker Orphan Detection Report"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Internal",
|
||||
"name": "internal",
|
||||
"items": [
|
||||
{
|
||||
"name": "report_root",
|
||||
"type": "str",
|
||||
"title": "Report Root",
|
||||
"required": false,
|
||||
"default": "/reports",
|
||||
"config": {
|
||||
"placeholder": "/reports"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orphan_dead_containers",
|
||||
"type": "str",
|
||||
"title": "Orphan Dead Containers",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "orphan_dangling_images",
|
||||
"type": "str",
|
||||
"title": "Orphan Dangling Images",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "orphan_dangling_volumes",
|
||||
"type": "str",
|
||||
"title": "Orphan Dangling Volumes",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "docker_orphan_report_body",
|
||||
"type": "str",
|
||||
"title": "Orphan Report Body",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "report_timestamp",
|
||||
"type": "str",
|
||||
"title": "Report Timestamp",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "orphan_exited_containers",
|
||||
"type": "str",
|
||||
"title": "Orphan Exited Containers",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "orphan_dangling_networks",
|
||||
"type": "str",
|
||||
"title": "Orphan Dangling Networks",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "report_file_name",
|
||||
"type": "str",
|
||||
"title": "Report File Name",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "discord_orphan_report_content",
|
||||
"type": "str",
|
||||
"title": "Orphan Report",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "inventory_hostname",
|
||||
"type": "str",
|
||||
"title": "Inventory Hostname",
|
||||
"required": false,
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
---
|
||||
- name: Docker postgres backup
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
backup_root: "/opt/docker-db-backups/postgres"
|
||||
backup_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
|
||||
backup_file_name: "backup_postgres_<< container_name >>_<< backup_timestamp >>.sql.gz"
|
||||
backup_file_path: "<< backup_root >>/<< backup_file_name >>"
|
||||
|
||||
tasks:
|
||||
- name: Ensure backup root exists
|
||||
ansible.builtin.file:
|
||||
path: "<< backup_root >>"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
|
||||
- name: Get container information
|
||||
community.docker.docker_container_info:
|
||||
name: "<< container_name >>"
|
||||
register: container_info
|
||||
|
||||
- name: Fail if container does not exist
|
||||
ansible.builtin.fail:
|
||||
msg: "Container '<< container_name >>' does not exist"
|
||||
when: not container_info.exists
|
||||
|
||||
- name: Run backup and validation
|
||||
block:
|
||||
- name: Dump all databases with password
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
docker exec -e PGPASSWORD=<< postgres_password | quote >> "<< container_name >>" \
|
||||
pg_dumpall -U "<< postgres_user | default('postgres') >>" \
|
||||
| gzip > "<< backup_file_path >>"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
when:
|
||||
- postgres_database | default('all') == 'all'
|
||||
- (postgres_password | default('')) | length > 0
|
||||
|
||||
- name: Dump all databases without password
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
docker exec "<< container_name >>" \
|
||||
pg_dumpall -U "<< postgres_user | default('postgres') >>" \
|
||||
| gzip > "<< backup_file_path >>"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
when:
|
||||
- postgres_database | default('all') == 'all'
|
||||
- (postgres_password | default('')) | length == 0
|
||||
|
||||
- name: Dump selected database with password
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
docker exec -e PGPASSWORD=<< postgres_password | quote >> "<< container_name >>" \
|
||||
pg_dump -U "<< postgres_user | default('postgres') >>" \
|
||||
"<< postgres_database >>" \
|
||||
| gzip > "<< backup_file_path >>"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
when:
|
||||
- postgres_database | default('all') != 'all'
|
||||
- (postgres_password | default('')) | length > 0
|
||||
|
||||
- name: Dump selected database without password
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
docker exec "<< container_name >>" \
|
||||
pg_dump -U "<< postgres_user | default('postgres') >>" \
|
||||
"<< postgres_database >>" \
|
||||
| gzip > "<< backup_file_path >>"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
when:
|
||||
- postgres_database | default('all') != 'all'
|
||||
- (postgres_password | default('')) | length == 0
|
||||
|
||||
- name: Validate backup file exists
|
||||
ansible.builtin.stat:
|
||||
path: "<< backup_file_path >>"
|
||||
register: backup_archive_stat
|
||||
|
||||
- name: Fail if backup file is missing or empty
|
||||
ansible.builtin.fail:
|
||||
msg: "Backup file missing or empty: << backup_file_path >>"
|
||||
when:
|
||||
- not (backup_archive_stat.stat.exists | default(false))
|
||||
or (backup_archive_stat.stat.size | default(0) | int) == 0
|
||||
|
||||
- name: Validate gzip archive integrity
|
||||
ansible.builtin.command: gzip -t "<< backup_file_path >>"
|
||||
changed_when: false
|
||||
|
||||
- name: Validate postgres dump signature
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
gzip -dc "<< backup_file_path >>" \
|
||||
| head -n 50 \
|
||||
| grep -Eq 'PostgreSQL database dump'
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
|
||||
- name: Remove old postgres backup archives
|
||||
ansible.builtin.find:
|
||||
paths: "<< backup_root >>"
|
||||
patterns: "backup_postgres_<< container_name >>_*.sql.gz"
|
||||
age: "<< backup_retention_days | default(14) >>d"
|
||||
recurse: true
|
||||
register: old_backups
|
||||
|
||||
- name: Delete old postgres backup archives
|
||||
ansible.builtin.file:
|
||||
path: "<< item.path >>"
|
||||
state: absent
|
||||
loop: "<< old_backups.files >>"
|
||||
|
||||
- name: Mark backup status success
|
||||
ansible.builtin.set_fact:
|
||||
backup_status: "success"
|
||||
backup_status_message: "Backup and validation succeeded"
|
||||
rescue:
|
||||
- name: Mark backup status failed
|
||||
ansible.builtin.set_fact:
|
||||
backup_status: "failed"
|
||||
backup_status_message: "<< ansible_failed_result.msg | default('Backup or validation failed') >>"
|
||||
always:
|
||||
- name: Send postgres backup status to Discord
|
||||
ansible.builtin.uri:
|
||||
url: "<< discord_webhook | default('') >>"
|
||||
method: POST
|
||||
body_format: json
|
||||
body:
|
||||
content: |
|
||||
PostgreSQL Backup << backup_status | default('failed') | upper >>
|
||||
Host: << inventory_hostname >>
|
||||
Container: << container_name >>
|
||||
File: << backup_file_path >>
|
||||
Message: << backup_status_message | default('n/a') >>
|
||||
status_code: 204
|
||||
when:
|
||||
- send_discord_notification | default(false)
|
||||
- (discord_webhook | default('')) | length > 0
|
||||
|
||||
- name: Fail play if backup failed
|
||||
ansible.builtin.fail:
|
||||
msg: "<< backup_status_message | default('Backup failed') >>"
|
||||
when: backup_status | default('failed') == 'failed'
|
||||
@@ -1,2 +0,0 @@
|
||||
collections:
|
||||
- name: community.docker
|
||||
@@ -1,187 +0,0 @@
|
||||
{
|
||||
"slug": "docker-postgres-backup",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Docker Postgres Backup",
|
||||
"description": "Connects to a named PostgreSQL container and creates compressed SQL dumps with configurable retention. Supports optional Discord notifications on success or failure.",
|
||||
"tags": [
|
||||
"docker",
|
||||
"backup",
|
||||
"postgres",
|
||||
"database"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "postgresql",
|
||||
"color": "emerald"
|
||||
},
|
||||
"draft": true,
|
||||
"version": {
|
||||
"name": "1",
|
||||
"source_dep_name": "manual/docker-postgres-backup"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Backup",
|
||||
"name": "backup",
|
||||
"items": [
|
||||
{
|
||||
"name": "container_name",
|
||||
"type": "str",
|
||||
"title": "Container Name",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "postgres_user",
|
||||
"type": "str",
|
||||
"title": "Postgres User",
|
||||
"required": false,
|
||||
"default": "postgres",
|
||||
"description": "Database user for pg_dump/pg_dumpall",
|
||||
"config": {
|
||||
"placeholder": "postgres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "postgres_password",
|
||||
"type": "str",
|
||||
"title": "Postgres Password",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "postgres_database",
|
||||
"type": "str",
|
||||
"title": "Database Name",
|
||||
"required": false,
|
||||
"default": "all",
|
||||
"description": "Database to dump, or 'all' for all databases",
|
||||
"config": {
|
||||
"placeholder": "all"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "backup_retention_days",
|
||||
"type": "int",
|
||||
"title": "Retention Days",
|
||||
"required": false,
|
||||
"default": 14,
|
||||
"description": "Delete backup archives older than this many days",
|
||||
"config": {
|
||||
"slider": true,
|
||||
"min": 1,
|
||||
"max": 365,
|
||||
"step": 1,
|
||||
"placeholder": "14",
|
||||
"unit": "days"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Notification",
|
||||
"name": "notification",
|
||||
"items": [
|
||||
{
|
||||
"name": "send_discord_notification",
|
||||
"type": "bool",
|
||||
"title": "Send Discord Notification",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Send success/failure status to Discord webhook"
|
||||
},
|
||||
{
|
||||
"name": "discord_webhook",
|
||||
"type": "secret",
|
||||
"title": "Discord Webhook",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Discord webhook URL for notifications"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Internal",
|
||||
"name": "internal",
|
||||
"items": [
|
||||
{
|
||||
"name": "backup_status",
|
||||
"type": "str",
|
||||
"title": "Backup Status",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "inventory_hostname",
|
||||
"type": "str",
|
||||
"title": "Inventory Hostname",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "backup_timestamp",
|
||||
"type": "str",
|
||||
"title": "Backup Timestamp",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "item",
|
||||
"type": "str",
|
||||
"title": "Loop Item",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "backup_file_path",
|
||||
"type": "str",
|
||||
"title": "Backup File Path",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "old_backups",
|
||||
"type": "str",
|
||||
"title": "Old Backups",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "backup_root",
|
||||
"type": "str",
|
||||
"title": "Backup Root",
|
||||
"required": false,
|
||||
"default": "/backups/postgres",
|
||||
"config": {
|
||||
"placeholder": "/backups/postgres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "backup_file_name",
|
||||
"type": "str",
|
||||
"title": "Backup File Name",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "ansible_failed_result",
|
||||
"type": "str",
|
||||
"title": "Failed Result",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "backup_status_message",
|
||||
"type": "str",
|
||||
"title": "Backup Status Message",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"config": {
|
||||
"textarea": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
---
|
||||
- name: Docker volume backup
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
backup_root: "/opt/docker-volume-backups"
|
||||
backup_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
|
||||
backup_archive_name: "backup_<< container_name >>_{{ backup_timestamp }}.tar.gz"
|
||||
|
||||
tasks:
|
||||
- name: Ensure backup root exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ backup_root }}"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
|
||||
- name: Get container information
|
||||
community.docker.docker_container_info:
|
||||
name: "<< container_name >>"
|
||||
register: container_info
|
||||
|
||||
- name: Fail if container does not exist
|
||||
ansible.builtin.fail:
|
||||
msg: "Container '<< container_name >>' does not exist"
|
||||
when: not container_info.exists
|
||||
|
||||
- name: Record container running state
|
||||
ansible.builtin.set_fact:
|
||||
container_was_running: "{{ (container_info.container.State.Status | default('')) == 'running' }}"
|
||||
|
||||
- name: Stop container for consistent backup
|
||||
community.docker.docker_container:
|
||||
name: "<< container_name >>"
|
||||
state: stopped
|
||||
when:
|
||||
- stop_containers_during_backup | default(false)
|
||||
- container_was_running
|
||||
|
||||
- name: Back up container path using all attached volumes
|
||||
ansible.builtin.shell: |
|
||||
set -eu
|
||||
docker run --rm \
|
||||
--volumes-from "<< container_name >>" \
|
||||
-v "{{ backup_root }}":/backup \
|
||||
busybox sh -c '
|
||||
test -e "<< backup_container_path >>"
|
||||
tar -czf "/backup/{{ backup_archive_name }}" \
|
||||
-C / "<< backup_container_path | regex_replace('^/', '') >>"
|
||||
'
|
||||
args:
|
||||
executable: /bin/sh
|
||||
changed_when: true
|
||||
|
||||
- name: Start container after backup
|
||||
community.docker.docker_container:
|
||||
name: "<< container_name >>"
|
||||
state: started
|
||||
when:
|
||||
- stop_containers_during_backup | default(false)
|
||||
- container_was_running
|
||||
|
||||
- name: Remove old backup archives
|
||||
ansible.builtin.find:
|
||||
paths: "{{ backup_root }}"
|
||||
patterns: "backup_<< container_name >>_*.tar.gz"
|
||||
age: "<< backup_retention_days | default(14) >>d"
|
||||
recurse: true
|
||||
register: old_backups
|
||||
|
||||
- name: Delete old backup archives
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
loop: "{{ old_backups.files }}"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
collections:
|
||||
- name: community.docker
|
||||
@@ -1,113 +0,0 @@
|
||||
{
|
||||
"slug": "docker-volume-backup",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Docker Volume Backup",
|
||||
"description": "Backs up data from a Docker container path by auto-attaching all container volumes into a temporary backup container. Creates timestamped tar.gz archives with configurable retention.",
|
||||
"tags": [
|
||||
"docker",
|
||||
"backup",
|
||||
"volume",
|
||||
"container"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "mdi",
|
||||
"id": "harddisk",
|
||||
"color": "emerald"
|
||||
},
|
||||
"draft": true,
|
||||
"version": {
|
||||
"name": "1",
|
||||
"source_dep_name": "manual/docker-volume-backup"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Backup",
|
||||
"name": "backup",
|
||||
"items": [
|
||||
{
|
||||
"name": "container_name",
|
||||
"type": "str",
|
||||
"title": "Container Name",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "backup_retention_days",
|
||||
"type": "int",
|
||||
"title": "Retention Days",
|
||||
"required": false,
|
||||
"default": 14,
|
||||
"description": "Delete backup archives older than this many days",
|
||||
"config": {
|
||||
"slider": true,
|
||||
"min": 1,
|
||||
"max": 365,
|
||||
"step": 1,
|
||||
"placeholder": "14",
|
||||
"unit": "days"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "backup_container_path",
|
||||
"type": "str",
|
||||
"title": "Backup Path",
|
||||
"required": true,
|
||||
"description": "Path inside the container that should be archived"
|
||||
},
|
||||
{
|
||||
"name": "stop_containers_during_backup",
|
||||
"type": "bool",
|
||||
"title": "Stop Backup",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "If enabled, stop the container before backup and start it again afterwards if it was running"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Internal",
|
||||
"name": "internal",
|
||||
"items": [
|
||||
{
|
||||
"name": "backup_timestamp",
|
||||
"type": "str",
|
||||
"title": "Backup Timestamp",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "backup_root",
|
||||
"type": "str",
|
||||
"title": "Backup Root",
|
||||
"required": false,
|
||||
"default": "/backups",
|
||||
"config": {
|
||||
"placeholder": "/backups"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "backup_archive_name",
|
||||
"type": "str",
|
||||
"title": "Backup Archive Name",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "item",
|
||||
"type": "str",
|
||||
"title": "Loop Item",
|
||||
"required": false,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "old_backups",
|
||||
"type": "str",
|
||||
"title": "Old Backups",
|
||||
"required": false,
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,404 +0,0 @@
|
||||
---
|
||||
- name: Configure K3s post-install platform services - Fetch kubeconfig
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
become: true
|
||||
<%- if secrets_file %>
|
||||
vars_files:
|
||||
- << secrets_file >>
|
||||
<%- endif %>
|
||||
|
||||
vars:
|
||||
kubeconfig_path: "<< kubeconfig_local_path >>"
|
||||
kubeconfig_server_target: "<< kubeconfig_server_endpoint >>"
|
||||
|
||||
tasks:
|
||||
- name: Validate that the kubeconfig source host exists in inventory
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'<< kubeconfig_source_host | default('k3s-server-01') >>' in hostvars"
|
||||
fail_msg: "Inventory host '<< kubeconfig_source_host | default('k3s-server-01') >>' was not found."
|
||||
run_once: true
|
||||
|
||||
- name: Read kubeconfig from the primary K3s server
|
||||
ansible.builtin.slurp:
|
||||
src: "<< kubeconfig_source_path | default('/etc/rancher/k3s/k3s.yaml') >>"
|
||||
delegate_to: "<< kubeconfig_source_host | default('k3s-server-01') >>"
|
||||
register: k3s_source_kubeconfig
|
||||
when: fetch_kubeconfig | default(true)
|
||||
run_once: true
|
||||
|
||||
- name: Ensure local kubeconfig directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ kubeconfig_path | dirname }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
when: fetch_kubeconfig | default(true)
|
||||
|
||||
- name: Write kubeconfig to the control machine
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ kubeconfig_path }}"
|
||||
mode: "0600"
|
||||
content: "{{ (k3s_source_kubeconfig.content | b64decode) | regex_replace('https://127\\.0\\.0\\.1:6443', 'https://' ~ kubeconfig_server_target ~ ':6443') }}"
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
when: fetch_kubeconfig | default(true)
|
||||
|
||||
- name: Configure K3s post-install platform services - Prepare Longhorn prerequisites
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
become: true
|
||||
<%- if secrets_file %>
|
||||
vars_files:
|
||||
- << secrets_file >>
|
||||
<%- endif %>
|
||||
vars:
|
||||
longhorn_target_hosts: "{{ query('inventory_hostnames', '<< longhorn_node_hosts >>') }}"
|
||||
|
||||
tasks:
|
||||
- name: Validate Longhorn target host pattern
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- longhorn_target_hosts | length > 0
|
||||
fail_msg: "No inventory hosts matched '<< longhorn_node_hosts >>'."
|
||||
when: longhorn_prerequisites_enabled | default(false)
|
||||
run_once: true
|
||||
|
||||
- name: Install Longhorn prerequisite packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- open-iscsi
|
||||
- nfs-common
|
||||
- cryptsetup
|
||||
- util-linux
|
||||
state: present
|
||||
update_cache: true
|
||||
when:
|
||||
- longhorn_prerequisites_enabled | default(false)
|
||||
- inventory_hostname in longhorn_target_hosts
|
||||
|
||||
- name: Ensure iscsid service is enabled and running
|
||||
ansible.builtin.service:
|
||||
name: iscsid
|
||||
state: started
|
||||
enabled: true
|
||||
when:
|
||||
- longhorn_prerequisites_enabled | default(false)
|
||||
- inventory_hostname in longhorn_target_hosts
|
||||
|
||||
- name: Ensure open-iscsi service is enabled and running when present
|
||||
ansible.builtin.service:
|
||||
name: open-iscsi
|
||||
state: started
|
||||
enabled: true
|
||||
failed_when: false
|
||||
when:
|
||||
- longhorn_prerequisites_enabled | default(false)
|
||||
- inventory_hostname in longhorn_target_hosts
|
||||
|
||||
- name: Persist dm_crypt kernel module for Longhorn
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/modules-load.d/longhorn.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
dm_crypt
|
||||
when:
|
||||
- longhorn_prerequisites_enabled | default(false)
|
||||
- inventory_hostname in longhorn_target_hosts
|
||||
|
||||
- name: Load dm_crypt kernel module immediately
|
||||
community.general.modprobe:
|
||||
name: dm_crypt
|
||||
state: present
|
||||
when:
|
||||
- longhorn_prerequisites_enabled | default(false)
|
||||
- inventory_hostname in longhorn_target_hosts
|
||||
|
||||
- name: Disable multipathd when the environment does not use multipath storage
|
||||
ansible.builtin.service:
|
||||
name: multipathd
|
||||
state: stopped
|
||||
enabled: false
|
||||
failed_when: false
|
||||
when:
|
||||
- longhorn_prerequisites_enabled | default(false)
|
||||
- longhorn_disable_multipathd | default(false)
|
||||
- inventory_hostname in longhorn_target_hosts
|
||||
|
||||
- name: Configure K3s post-install platform services - Configure cluster add-ons
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
<%- if secrets_file %>
|
||||
vars_files:
|
||||
- << secrets_file >>
|
||||
<%- endif %>
|
||||
|
||||
vars:
|
||||
kubeconfig_path: "<< kubeconfig_local_path >>"
|
||||
|
||||
tasks:
|
||||
- block:
|
||||
- name: Ensure the kubeconfig file exists before applying cluster resources
|
||||
ansible.builtin.stat:
|
||||
path: "{{ kubeconfig_path }}"
|
||||
register: kubeconfig_stat
|
||||
|
||||
- name: Fail when the kubeconfig file is missing
|
||||
ansible.builtin.fail:
|
||||
msg: "Kubeconfig not found at {{ kubeconfig_path }}. Enable fetch_kubeconfig or provide an existing kubeconfig path."
|
||||
when: not (kubeconfig_stat.stat.exists | default(false))
|
||||
|
||||
- name: Verify Helm is available on the control machine when required
|
||||
ansible.builtin.command:
|
||||
cmd: helm version --short
|
||||
changed_when: false
|
||||
when: (cert_manager_install_enabled | default(false)) or (metrics_server_install_enabled | default(false)) or (longhorn_install_enabled | default(false))
|
||||
|
||||
- name: Validate ACME email when ClusterIssuer creation is enabled
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (cert_manager_acme_email | default('')) | length > 0
|
||||
fail_msg: "cert_manager_acme_email must be set when cert_manager_clusterissuer_enabled=true."
|
||||
when: cert_manager_clusterissuer_enabled | default(false)
|
||||
|
||||
- name: Build namespace list for enabled platform components
|
||||
ansible.builtin.set_fact:
|
||||
managed_namespaces: >-
|
||||
{{
|
||||
(
|
||||
(extra_namespaces_yaml | default('[]') | from_yaml)
|
||||
+ ([cert_manager_namespace | default('cert-manager')] if cert_manager_install_enabled | default(false) or cert_manager_create_cloudflare_secret | default(false) or cert_manager_clusterissuer_enabled | default(false) else [])
|
||||
+ ([longhorn_namespace | default('longhorn-system')] if longhorn_install_enabled | default(false) else [])
|
||||
+ ([traefik_dashboard_namespace | default('traefik')] if traefik_dashboard_exposure_enabled | default(false) else [])
|
||||
) | unique
|
||||
}}
|
||||
when: ensure_namespaces | default(true)
|
||||
|
||||
- name: Ensure selected namespaces exist
|
||||
kubernetes.core.k8s:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ item }}"
|
||||
loop: "{{ managed_namespaces | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when:
|
||||
- ensure_namespaces | default(true)
|
||||
- item | length > 0
|
||||
|
||||
- name: Add cert-manager Helm repository
|
||||
kubernetes.core.helm_repository:
|
||||
name: jetstack
|
||||
repo_url: https://charts.jetstack.io
|
||||
when: cert_manager_install_enabled | default(false)
|
||||
|
||||
- name: Install cert-manager
|
||||
kubernetes.core.helm:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
name: cert-manager
|
||||
chart_ref: jetstack/cert-manager
|
||||
chart_version: "<< cert_manager_chart_version | default('1.18.2') >>"
|
||||
release_namespace: "<< cert_manager_namespace | default('cert-manager') >>"
|
||||
create_namespace: true
|
||||
wait: true
|
||||
values:
|
||||
crds:
|
||||
enabled: true
|
||||
when: cert_manager_install_enabled | default(false)
|
||||
|
||||
- name: Create Cloudflare API token secret placeholder for cert-manager
|
||||
kubernetes.core.k8s:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "<< cert_manager_cloudflare_secret_name | default('cloudflare-api-token-secret') >>"
|
||||
namespace: "<< cert_manager_namespace | default('cert-manager') >>"
|
||||
type: Opaque
|
||||
stringData:
|
||||
<< cert_manager_cloudflare_secret_key | default('api-token') >>: "<< cert_manager_cloudflare_api_token | default('replace-me-with-your-cloudflare-api-token') >>"
|
||||
when: cert_manager_create_cloudflare_secret | default(false)
|
||||
|
||||
- name: Create cert-manager ClusterIssuer
|
||||
kubernetes.core.k8s:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: "<< cert_manager_clusterissuer_name | default('letsencrypt-production') >>"
|
||||
spec:
|
||||
acme:
|
||||
email: "<< cert_manager_acme_email >>"
|
||||
server: "<< cert_manager_acme_server | default('https://acme-v02.api.letsencrypt.org/directory') >>"
|
||||
privateKeySecretRef:
|
||||
name: "<< cert_manager_clusterissuer_name | default('letsencrypt-production') >>-account-key"
|
||||
solvers:
|
||||
- dns01:
|
||||
cloudflare:
|
||||
apiTokenSecretRef:
|
||||
name: "<< cert_manager_cloudflare_secret_name | default('cloudflare-api-token-secret') >>"
|
||||
key: "<< cert_manager_cloudflare_secret_key | default('api-token') >>"
|
||||
when: cert_manager_clusterissuer_enabled | default(false)
|
||||
|
||||
- name: Add metrics-server Helm repository
|
||||
kubernetes.core.helm_repository:
|
||||
name: metrics-server
|
||||
repo_url: https://kubernetes-sigs.github.io/metrics-server/
|
||||
when: metrics_server_install_enabled | default(false)
|
||||
|
||||
- name: Install metrics-server
|
||||
kubernetes.core.helm:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
name: metrics-server
|
||||
chart_ref: metrics-server/metrics-server
|
||||
chart_version: "<< metrics_server_chart_version | default('3.13.0') >>"
|
||||
release_namespace: "<< metrics_server_namespace | default('kube-system') >>"
|
||||
create_namespace: true
|
||||
wait: true
|
||||
<%- if metrics_server_kubelet_insecure_tls %>
|
||||
values:
|
||||
args:
|
||||
- --kubelet-insecure-tls
|
||||
<%- endif %>
|
||||
when: metrics_server_install_enabled | default(false)
|
||||
|
||||
- name: Add Longhorn Helm repository
|
||||
kubernetes.core.helm_repository:
|
||||
name: longhorn
|
||||
repo_url: https://charts.longhorn.io
|
||||
when: longhorn_install_enabled | default(false)
|
||||
|
||||
- name: Install Longhorn
|
||||
kubernetes.core.helm:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
name: longhorn
|
||||
chart_ref: longhorn/longhorn
|
||||
chart_version: "<< longhorn_chart_version | default('1.9.1') >>"
|
||||
release_namespace: "<< longhorn_namespace | default('longhorn-system') >>"
|
||||
create_namespace: true
|
||||
wait: true
|
||||
values:
|
||||
defaultSettings:
|
||||
defaultDataPath: "<< longhorn_default_data_path | default('/var/lib/longhorn') >>"
|
||||
when: longhorn_install_enabled | default(false)
|
||||
|
||||
- name: Expose Traefik dashboard through an IngressRoute
|
||||
kubernetes.core.k8s:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: traefik-dashboard
|
||||
namespace: "<< traefik_dashboard_namespace | default('traefik') >>"
|
||||
spec:
|
||||
entryPoints:
|
||||
- "<< traefik_dashboard_entrypoint | default('websecure') >>"
|
||||
routes:
|
||||
- match: "Host(`<< traefik_dashboard_host | default('traefik.example.internal') >>`)"
|
||||
kind: Rule
|
||||
<%- if traefik_dashboard_middleware_name %>
|
||||
middlewares:
|
||||
- name: "<< traefik_dashboard_middleware_name >>"
|
||||
namespace: "<< traefik_dashboard_middleware_namespace | default('traefik') >>"
|
||||
<%- endif %>
|
||||
services:
|
||||
- kind: TraefikService
|
||||
name: api@internal
|
||||
<%- if traefik_dashboard_tls_enabled %>
|
||||
tls:
|
||||
secretName: "<< traefik_dashboard_tls_secret_name | default('traefik-dashboard-tls') >>"
|
||||
<%- endif %>
|
||||
when: traefik_dashboard_exposure_enabled | default(false)
|
||||
|
||||
- name: Read kube-proxy ConfigMap before strictARP patch
|
||||
kubernetes.core.k8s_info:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
api_version: v1
|
||||
kind: ConfigMap
|
||||
namespace: kube-system
|
||||
name: kube-proxy
|
||||
register: kube_proxy_configmap
|
||||
when: kube_proxy_strict_arp_enabled | default(false)
|
||||
|
||||
- name: Build kube-proxy configuration with strictARP enabled
|
||||
ansible.builtin.set_fact:
|
||||
kube_proxy_rendered_config: >-
|
||||
{{
|
||||
(kube_proxy_configmap.resources[0].data['config.conf'] | from_yaml)
|
||||
| combine(
|
||||
{
|
||||
'ipvs': (
|
||||
(kube_proxy_configmap.resources[0].data['config.conf'] | from_yaml).ipvs
|
||||
| default({})
|
||||
| combine({'strictARP': true})
|
||||
)
|
||||
},
|
||||
recursive=True
|
||||
)
|
||||
}}
|
||||
when:
|
||||
- kube_proxy_strict_arp_enabled | default(false)
|
||||
- kube_proxy_configmap.resources | length > 0
|
||||
|
||||
- name: Patch kube-proxy ConfigMap with strictARP enabled
|
||||
kubernetes.core.k8s:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
state: present
|
||||
merge_type:
|
||||
- merge
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: kube-proxy
|
||||
namespace: kube-system
|
||||
data:
|
||||
config.conf: "{{ kube_proxy_rendered_config | to_nice_yaml(indent=2) }}"
|
||||
when:
|
||||
- kube_proxy_strict_arp_enabled | default(false)
|
||||
- kube_proxy_configmap.resources | length > 0
|
||||
|
||||
- name: Read kube-proxy daemonset for restart
|
||||
kubernetes.core.k8s_info:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
api_version: apps/v1
|
||||
kind: DaemonSet
|
||||
namespace: kube-system
|
||||
name: kube-proxy
|
||||
register: kube_proxy_daemonset
|
||||
when: kube_proxy_strict_arp_enabled | default(false)
|
||||
|
||||
- name: Restart kube-proxy daemonset after strictARP change
|
||||
kubernetes.core.k8s:
|
||||
kubeconfig: "{{ kubeconfig_path }}"
|
||||
state: present
|
||||
merge_type:
|
||||
- merge
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: kube-proxy
|
||||
namespace: kube-system
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
homelab.boilerplates.io/restarted-at: "{{ lookup('pipe', 'date -u +%Y-%m-%dT%H:%M:%SZ') }}"
|
||||
when:
|
||||
- kube_proxy_strict_arp_enabled | default(false)
|
||||
- kube_proxy_daemonset.resources | length > 0
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
@@ -1,527 +0,0 @@
|
||||
{
|
||||
"slug": "k3s-postinstall",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "K3s Post-Install Platform Setup",
|
||||
"description": "Configures common post-install building blocks for a fresh K3s cluster. Supports kubeconfig retrieval, namespace management, Traefik dashboard exposure, cert-manager bootstrap, metrics-server, Longhorn preparation, and optional kube-system tuning.",
|
||||
"tags": [
|
||||
"kubernetes",
|
||||
"k3s",
|
||||
"postinstall",
|
||||
"helm",
|
||||
"homelab"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "kubernetes"
|
||||
},
|
||||
"draft": false,
|
||||
"version": {
|
||||
"name": "1.0.0",
|
||||
"source_dep_name": "manual/k3s-postinstall"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Execution",
|
||||
"name": "execution",
|
||||
"items": [
|
||||
{
|
||||
"name": "secrets_file",
|
||||
"type": "str",
|
||||
"title": "Secrets File",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Optional vars file to load before running the playbook."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Kubeconfig",
|
||||
"name": "kubeconfig",
|
||||
"items": [
|
||||
{
|
||||
"name": "fetch_kubeconfig",
|
||||
"type": "bool",
|
||||
"title": "Fetch Kubeconfig",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Fetch the admin kubeconfig from the first K3s server before applying cluster resources"
|
||||
},
|
||||
{
|
||||
"name": "kubeconfig_source_host",
|
||||
"type": "str",
|
||||
"title": "Source Host",
|
||||
"required": false,
|
||||
"default": "k3s-server-01",
|
||||
"description": "Inventory host that stores the K3s admin kubeconfig",
|
||||
"needs": [
|
||||
"fetch_kubeconfig=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "k3s-server-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kubeconfig_source_path",
|
||||
"type": "str",
|
||||
"title": "Source Path",
|
||||
"required": false,
|
||||
"default": "/etc/rancher/k3s/k3s.yaml",
|
||||
"description": "Path to the kubeconfig file on the K3s server",
|
||||
"needs": [
|
||||
"fetch_kubeconfig=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "/etc/rancher/k3s/k3s.yaml"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kubeconfig_server_endpoint",
|
||||
"type": "str",
|
||||
"title": "API Endpoint",
|
||||
"required": false,
|
||||
"default": "{{ hostvars['k3s-server-01'].ansible_host | default('k3s-server-01') }}",
|
||||
"description": "Host or IP that should replace 127.0.0.1 in the fetched kubeconfig",
|
||||
"needs": [
|
||||
"fetch_kubeconfig=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "192.168.1.10"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kubeconfig_local_path",
|
||||
"type": "str",
|
||||
"title": "Local Kubeconfig",
|
||||
"required": false,
|
||||
"default": "{{ lookup('env', 'HOME') }}/.kube/k3s.yaml",
|
||||
"description": "Local kubeconfig path used by the control machine for all cluster operations",
|
||||
"config": {
|
||||
"placeholder": "{{ lookup('env', 'HOME') }}/.kube/k3s.yaml"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Namespaces",
|
||||
"name": "namespaces",
|
||||
"items": [
|
||||
{
|
||||
"name": "ensure_namespaces",
|
||||
"type": "bool",
|
||||
"title": "Manage Namespaces",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Ensure common namespaces exist before installing platform add-ons"
|
||||
},
|
||||
{
|
||||
"name": "extra_namespaces_yaml",
|
||||
"type": "str",
|
||||
"title": "Extra Namespaces",
|
||||
"required": false,
|
||||
"default": "[]",
|
||||
"description": "YAML list of extra namespaces to create",
|
||||
"needs": [
|
||||
"ensure_namespaces=true"
|
||||
],
|
||||
"config": {
|
||||
"textarea": true,
|
||||
"placeholder": "- apps\n- monitoring"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Traefik",
|
||||
"name": "traefik",
|
||||
"items": [
|
||||
{
|
||||
"name": "traefik_dashboard_exposure_enabled",
|
||||
"type": "bool",
|
||||
"title": "Expose Dashboard",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Create a Traefik IngressRoute that exposes the internal Traefik dashboard"
|
||||
},
|
||||
{
|
||||
"name": "traefik_dashboard_namespace",
|
||||
"type": "str",
|
||||
"title": "Namespace",
|
||||
"required": false,
|
||||
"default": "traefik",
|
||||
"description": "Namespace where the Traefik release is installed",
|
||||
"needs": [
|
||||
"traefik_dashboard_exposure_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "traefik"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "traefik_dashboard_host",
|
||||
"type": "str",
|
||||
"title": "Dashboard Host",
|
||||
"required": false,
|
||||
"default": "traefik.example.internal",
|
||||
"description": "FQDN used to expose the Traefik dashboard",
|
||||
"needs": [
|
||||
"traefik_dashboard_exposure_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "traefik.example.internal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "traefik_dashboard_entrypoint",
|
||||
"type": "str",
|
||||
"title": "Entrypoint",
|
||||
"required": false,
|
||||
"default": "websecure",
|
||||
"description": "Traefik entrypoint for the dashboard route",
|
||||
"needs": [
|
||||
"traefik_dashboard_exposure_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "websecure"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "traefik_dashboard_tls_enabled",
|
||||
"type": "bool",
|
||||
"title": "TLS",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"needs": [
|
||||
"traefik_dashboard_exposure_enabled=true"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "traefik_dashboard_tls_secret_name",
|
||||
"type": "str",
|
||||
"title": "TLS Secret",
|
||||
"required": false,
|
||||
"default": "traefik-dashboard-tls",
|
||||
"description": "TLS secret used by the dashboard route when TLS is enabled",
|
||||
"needs": [
|
||||
"traefik_dashboard_exposure_enabled=true",
|
||||
"traefik_dashboard_tls_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "traefik-dashboard-tls"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "traefik_dashboard_middleware_name",
|
||||
"type": "str",
|
||||
"title": "Middleware",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Optional Traefik middleware name attached to the dashboard route",
|
||||
"needs": [
|
||||
"traefik_dashboard_exposure_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "traefik-dashboard-auth"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "traefik_dashboard_middleware_namespace",
|
||||
"type": "str",
|
||||
"title": "Middleware NS",
|
||||
"required": false,
|
||||
"default": "traefik",
|
||||
"needs": [
|
||||
"traefik_dashboard_exposure_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "traefik"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Cert-Manager",
|
||||
"name": "cert-manager",
|
||||
"items": [
|
||||
{
|
||||
"name": "cert_manager_install_enabled",
|
||||
"type": "bool",
|
||||
"title": "Install cert-manager",
|
||||
"required": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "cert_manager_chart_version",
|
||||
"type": "str",
|
||||
"title": "Chart Version",
|
||||
"required": false,
|
||||
"default": "1.18.2",
|
||||
"needs": [
|
||||
"cert_manager_install_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "1.18.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cert_manager_namespace",
|
||||
"type": "str",
|
||||
"title": "Namespace",
|
||||
"required": false,
|
||||
"default": "cert-manager",
|
||||
"config": {
|
||||
"placeholder": "cert-manager"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cert_manager_create_cloudflare_secret",
|
||||
"type": "bool",
|
||||
"title": "Cloudflare Secret",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Create a placeholder secret for a Cloudflare DNS API token"
|
||||
},
|
||||
{
|
||||
"name": "cert_manager_cloudflare_secret_name",
|
||||
"type": "str",
|
||||
"title": "Secret Name",
|
||||
"required": false,
|
||||
"default": "cloudflare-api-token-secret",
|
||||
"description": "Secret name for the Cloudflare DNS API token",
|
||||
"needs": [
|
||||
"cert_manager_create_cloudflare_secret=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "cloudflare-api-token-secret"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cert_manager_cloudflare_secret_key",
|
||||
"type": "str",
|
||||
"title": "Secret Key",
|
||||
"required": false,
|
||||
"default": "api-token",
|
||||
"description": "Secret key that stores the Cloudflare API token",
|
||||
"needs": [
|
||||
"cert_manager_create_cloudflare_secret=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "api-token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cert_manager_cloudflare_api_token",
|
||||
"type": "secret",
|
||||
"title": "Cloudflare Token",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Optional Cloudflare API token value used to populate the placeholder secret",
|
||||
"needs": [
|
||||
"cert_manager_create_cloudflare_secret=true"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cert_manager_clusterissuer_enabled",
|
||||
"type": "bool",
|
||||
"title": "ClusterIssuer",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Create a production Let's Encrypt ClusterIssuer that uses Cloudflare DNS-01"
|
||||
},
|
||||
{
|
||||
"name": "cert_manager_clusterissuer_name",
|
||||
"type": "str",
|
||||
"title": "ClusterIssuer Name",
|
||||
"required": false,
|
||||
"default": "letsencrypt-production",
|
||||
"needs": [
|
||||
"cert_manager_clusterissuer_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "letsencrypt-production"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cert_manager_acme_email",
|
||||
"type": "str",
|
||||
"title": "ACME Email",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Email address used for ACME registration",
|
||||
"needs": [
|
||||
"cert_manager_clusterissuer_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "me@example.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cert_manager_acme_server",
|
||||
"type": "str",
|
||||
"title": "ACME Server",
|
||||
"required": false,
|
||||
"default": "https://acme-v02.api.letsencrypt.org/directory",
|
||||
"description": "ACME directory URL used by the ClusterIssuer",
|
||||
"needs": [
|
||||
"cert_manager_clusterissuer_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "https://acme-v02.api.letsencrypt.org/directory"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Metrics Server",
|
||||
"name": "metrics-server",
|
||||
"items": [
|
||||
{
|
||||
"name": "metrics_server_install_enabled",
|
||||
"type": "bool",
|
||||
"title": "Install Metrics Server",
|
||||
"required": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "metrics_server_chart_version",
|
||||
"type": "str",
|
||||
"title": "Chart Version",
|
||||
"required": false,
|
||||
"default": "3.13.0",
|
||||
"needs": [
|
||||
"metrics_server_install_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "3.13.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "metrics_server_namespace",
|
||||
"type": "str",
|
||||
"title": "Namespace",
|
||||
"required": false,
|
||||
"default": "kube-system",
|
||||
"needs": [
|
||||
"metrics_server_install_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "kube-system"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "metrics_server_kubelet_insecure_tls",
|
||||
"type": "bool",
|
||||
"title": "Insecure TLS",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Add --kubelet-insecure-tls for clusters that use self-signed kubelet certificates",
|
||||
"needs": [
|
||||
"metrics_server_install_enabled=true"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Longhorn",
|
||||
"name": "longhorn",
|
||||
"items": [
|
||||
{
|
||||
"name": "longhorn_prerequisites_enabled",
|
||||
"type": "bool",
|
||||
"title": "Prepare Nodes",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Install host packages and kernel module configuration needed by Longhorn"
|
||||
},
|
||||
{
|
||||
"name": "longhorn_node_hosts",
|
||||
"type": "str",
|
||||
"title": "Node Hosts",
|
||||
"required": false,
|
||||
"default": "k3s_cluster",
|
||||
"description": "Inventory host pattern for the nodes that should be prepared for Longhorn",
|
||||
"needs": [
|
||||
"longhorn_prerequisites_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "k3s_cluster"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "longhorn_disable_multipathd",
|
||||
"type": "bool",
|
||||
"title": "Disable multipathd",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Stop and disable multipathd if your nodes do not use multipath storage",
|
||||
"needs": [
|
||||
"longhorn_prerequisites_enabled=true"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "longhorn_install_enabled",
|
||||
"type": "bool",
|
||||
"title": "Install Longhorn",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Install Longhorn with Helm after prerequisites are in place"
|
||||
},
|
||||
{
|
||||
"name": "longhorn_chart_version",
|
||||
"type": "str",
|
||||
"title": "Chart Version",
|
||||
"required": false,
|
||||
"default": "1.9.1",
|
||||
"needs": [
|
||||
"longhorn_install_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "1.9.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "longhorn_namespace",
|
||||
"type": "str",
|
||||
"title": "Namespace",
|
||||
"required": false,
|
||||
"default": "longhorn-system",
|
||||
"needs": [
|
||||
"longhorn_install_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "longhorn-system"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "longhorn_default_data_path",
|
||||
"type": "str",
|
||||
"title": "Data Path",
|
||||
"required": false,
|
||||
"default": "/var/lib/longhorn",
|
||||
"description": "Default host path used by Longhorn for replica data",
|
||||
"needs": [
|
||||
"longhorn_install_enabled=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "/var/lib/longhorn"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Kube-System",
|
||||
"name": "kube-system",
|
||||
"items": [
|
||||
{
|
||||
"name": "kube_proxy_strict_arp_enabled",
|
||||
"type": "bool",
|
||||
"title": "Enable strictARP",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Patch the kube-proxy ConfigMap and restart the daemonset with strictARP enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
---
|
||||
- name: Bootstrap K3s cluster with k3sup
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
<%- if secrets_file %>
|
||||
vars_files:
|
||||
- << secrets_file >>
|
||||
<%- endif %>
|
||||
|
||||
vars:
|
||||
k3s_server_target: "{{ hostvars['<< k3s_server_host >>'].ansible_host | default('<< k3s_server_host >>') }}"
|
||||
<%- if kubeconfig_destination_mode == 'remote' %>
|
||||
k3s_generated_kubeconfig_path: "/tmp/<< k3s_context_name | default('homelab-k3s') >>-kubeconfig"
|
||||
<%- else %>
|
||||
k3s_generated_kubeconfig_path: "<< local_kubeconfig_path >>"
|
||||
<%- endif %>
|
||||
|
||||
tasks:
|
||||
- name: Gather control machine facts
|
||||
ansible.builtin.setup:
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
register: localhost_facts
|
||||
|
||||
- name: Validate that the first server host exists in inventory
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'<< k3s_server_host >>' in hostvars"
|
||||
fail_msg: "Inventory host '<< k3s_server_host >>' was not found."
|
||||
run_once: true
|
||||
|
||||
- name: Resolve k3sup release asset
|
||||
ansible.builtin.set_fact:
|
||||
k3sup_release_asset: >-
|
||||
{%- if localhost_facts.ansible_facts.ansible_system == 'Linux' and localhost_facts.ansible_facts.ansible_architecture in ['x86_64', 'amd64'] -%}
|
||||
k3sup
|
||||
{%- elif localhost_facts.ansible_facts.ansible_system == 'Linux' and localhost_facts.ansible_facts.ansible_architecture in ['aarch64', 'arm64'] -%}
|
||||
k3sup-arm64
|
||||
{%- elif localhost_facts.ansible_facts.ansible_system == 'Linux' and localhost_facts.ansible_facts.ansible_architecture in ['armv7l', 'armv6l'] -%}
|
||||
k3sup-arm
|
||||
{%- elif localhost_facts.ansible_facts.ansible_system == 'Darwin' and localhost_facts.ansible_facts.ansible_architecture in ['x86_64', 'amd64'] -%}
|
||||
k3sup-darwin
|
||||
{%- elif localhost_facts.ansible_facts.ansible_system == 'Darwin' and localhost_facts.ansible_facts.ansible_architecture in ['arm64', 'aarch64'] -%}
|
||||
k3sup-darwin-arm64
|
||||
{%- else -%}
|
||||
unsupported
|
||||
{%- endif -%}
|
||||
when: install_k3sup_binary | default(false)
|
||||
run_once: true
|
||||
|
||||
- name: Fail when k3sup binary installation is not supported for this control machine
|
||||
ansible.builtin.fail:
|
||||
msg: "Automatic k3sup installation is not supported for {{ localhost_facts.ansible_facts.ansible_system }}/{{ localhost_facts.ansible_facts.ansible_architecture }}. Set install_k3sup_binary=false and install k3sup manually."
|
||||
when:
|
||||
- install_k3sup_binary | default(false)
|
||||
- k3sup_release_asset == "unsupported"
|
||||
run_once: true
|
||||
|
||||
- name: Ensure local directory for k3sup exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ '<< k3sup_install_path >>' | dirname }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
delegate_to: localhost
|
||||
become: true
|
||||
when: install_k3sup_binary | default(false)
|
||||
run_once: true
|
||||
|
||||
- name: Install k3sup binary on the control machine
|
||||
ansible.builtin.get_url:
|
||||
url: "https://github.com/alexellis/k3sup/releases/download/<< k3sup_version >>/{{ k3sup_release_asset }}"
|
||||
dest: "<< k3sup_install_path >>"
|
||||
mode: "0755"
|
||||
delegate_to: localhost
|
||||
become: true
|
||||
when: install_k3sup_binary | default(false)
|
||||
run_once: true
|
||||
|
||||
- name: Verify k3sup is available
|
||||
ansible.builtin.command:
|
||||
cmd: "<< k3sup_install_path >> version"
|
||||
changed_when: false
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
|
||||
- name: Ensure local kubeconfig directory exists when using local destination mode
|
||||
ansible.builtin.file:
|
||||
path: "{{ k3s_generated_kubeconfig_path | dirname }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
delegate_to: localhost
|
||||
when: "<< kubeconfig_destination_mode >>" == "local"
|
||||
run_once: true
|
||||
|
||||
- name: Check whether K3s is already present on the first server
|
||||
ansible.builtin.stat:
|
||||
path: /etc/rancher/k3s/k3s.yaml
|
||||
delegate_to: "<< k3s_server_host >>"
|
||||
register: k3s_server_install_state
|
||||
run_once: true
|
||||
|
||||
- name: Bootstrap the first K3s server with k3sup
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
<< k3sup_install_path >> install
|
||||
--ip {{ k3s_server_target }}
|
||||
--user << k3s_server_user | default('ubuntu') >>
|
||||
--ssh-port << k3s_server_ssh_port | default(22) >>
|
||||
--local-path {{ k3s_generated_kubeconfig_path }}
|
||||
--merge=false
|
||||
--context << k3s_context_name | default('homelab-k3s') >>
|
||||
<%- if k3sup_use_sudo %>
|
||||
--sudo
|
||||
<%- endif %>
|
||||
<%- if k3s_server_ssh_key_path %>
|
||||
--ssh-key << k3s_server_ssh_key_path | quote >>
|
||||
<%- endif %>
|
||||
<%- if extra_server_args %>
|
||||
--k3s-extra-args << extra_server_args | quote >>
|
||||
<%- endif %>
|
||||
delegate_to: localhost
|
||||
when:
|
||||
- bootstrap_control_plane | default(true)
|
||||
- not (k3s_server_install_state.stat.exists | default(false))
|
||||
run_once: true
|
||||
|
||||
- name: Resolve inventory hosts for K3s agents
|
||||
ansible.builtin.set_fact:
|
||||
k3s_agent_inventory_hosts: "{{ query('inventory_hostnames', '<< k3s_agent_hosts_pattern >>') }}"
|
||||
when: join_agent_nodes | default(true)
|
||||
run_once: true
|
||||
|
||||
- name: Fail when agent join is enabled but no hosts match the inventory pattern
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- k3s_agent_inventory_hosts | length > 0
|
||||
fail_msg: "No inventory hosts matched '<< k3s_agent_hosts_pattern >>'."
|
||||
when: join_agent_nodes | default(true)
|
||||
run_once: true
|
||||
|
||||
- name: Check whether K3s agent is already present on each node
|
||||
ansible.builtin.stat:
|
||||
path: /etc/systemd/system/k3s-agent.service
|
||||
delegate_to: "{{ item }}"
|
||||
loop: "{{ k3s_agent_inventory_hosts | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
register: k3s_agent_install_state
|
||||
when: join_agent_nodes | default(true)
|
||||
run_once: true
|
||||
|
||||
- name: Join agent nodes to the K3s cluster with k3sup
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
<< k3sup_install_path >> join
|
||||
--ip {{ hostvars[item.item].ansible_host | default(item.item) }}
|
||||
--server-ip {{ k3s_server_target }}
|
||||
--user << k3s_agent_user | default('ubuntu') >>
|
||||
--server-user << k3s_server_user | default('ubuntu') >>
|
||||
--ssh-port << k3s_agent_ssh_port | default(22) >>
|
||||
--server-ssh-port << k3s_server_ssh_port | default(22) >>
|
||||
<%- if k3sup_use_sudo %>
|
||||
--sudo
|
||||
<%- endif %>
|
||||
<%- if k3s_server_ssh_key_path %>
|
||||
--ssh-key << k3s_server_ssh_key_path | quote >>
|
||||
<%- endif %>
|
||||
<%- if extra_agent_args %>
|
||||
--k3s-extra-args << extra_agent_args | quote >>
|
||||
<%- endif %>
|
||||
delegate_to: localhost
|
||||
loop: "{{ k3s_agent_install_state.results | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when:
|
||||
- join_agent_nodes | default(true)
|
||||
- not (item.stat.exists | default(false))
|
||||
run_once: true
|
||||
|
||||
- name: Ensure remote kubeconfig directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ '<< kubeconfig_remote_path >>' | dirname }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
delegate_to: "<< kubeconfig_remote_host | default('localhost') >>"
|
||||
become: << kubeconfig_remote_become | default(false) >>
|
||||
when: "<< kubeconfig_destination_mode >>" == "remote"
|
||||
run_once: true
|
||||
|
||||
- name: Copy kubeconfig to the remote destination host
|
||||
ansible.builtin.copy:
|
||||
src: "{{ k3s_generated_kubeconfig_path }}"
|
||||
dest: "<< kubeconfig_remote_path >>"
|
||||
owner: "<< kubeconfig_remote_owner | default('ubuntu') >>"
|
||||
group: "<< kubeconfig_remote_group | default('ubuntu') >>"
|
||||
mode: "<< kubeconfig_remote_mode | default('0600') >>"
|
||||
delegate_to: "<< kubeconfig_remote_host | default('localhost') >>"
|
||||
become: << kubeconfig_remote_become | default(false) >>
|
||||
when: "<< kubeconfig_destination_mode >>" == "remote"
|
||||
run_once: true
|
||||
@@ -1,353 +0,0 @@
|
||||
{
|
||||
"slug": "k3sup-k3s-cluster",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Bootstrap K3s Cluster with k3sup",
|
||||
"description": "Bootstraps a K3s cluster from the Ansible control machine by using k3sup over SSH. Supports first-server install, optional agent joins, and local or remote kubeconfig placement.",
|
||||
"tags": [
|
||||
"kubernetes",
|
||||
"k3s",
|
||||
"k3sup",
|
||||
"bootstrap"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "kubernetes"
|
||||
},
|
||||
"draft": true,
|
||||
"version": {
|
||||
"name": "1.0.0",
|
||||
"source_dep_name": "manual/k3sup-k3s-cluster"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Execution",
|
||||
"name": "execution",
|
||||
"items": [
|
||||
{
|
||||
"name": "secrets_file",
|
||||
"type": "str",
|
||||
"title": "Secrets File",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Optional vars file to load before running the playbook."
|
||||
},
|
||||
{
|
||||
"name": "install_k3sup_binary",
|
||||
"type": "bool",
|
||||
"title": "Install k3sup",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Download and install the k3sup binary on the Ansible control machine before bootstrapping"
|
||||
},
|
||||
{
|
||||
"name": "k3sup_version",
|
||||
"type": "str",
|
||||
"title": "k3sup Version",
|
||||
"required": false,
|
||||
"default": "0.13.15",
|
||||
"description": "Version of k3sup to install when binary installation is enabled",
|
||||
"needs": [
|
||||
"install_k3sup_binary=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "0.13.15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "k3sup_install_path",
|
||||
"type": "str",
|
||||
"title": "k3sup Path",
|
||||
"required": false,
|
||||
"default": "/usr/local/bin/k3sup",
|
||||
"description": "Local filesystem path for the k3sup binary",
|
||||
"config": {
|
||||
"placeholder": "/usr/local/bin/k3sup"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Control Plane",
|
||||
"name": "control-plane",
|
||||
"items": [
|
||||
{
|
||||
"name": "bootstrap_control_plane",
|
||||
"type": "bool",
|
||||
"title": "Bootstrap Server",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Run k3sup install against the first K3s server"
|
||||
},
|
||||
{
|
||||
"name": "k3s_server_host",
|
||||
"type": "str",
|
||||
"title": "Server Host",
|
||||
"required": true,
|
||||
"default": "k3s-server-01",
|
||||
"description": "Inventory host name for the first K3s server",
|
||||
"config": {
|
||||
"placeholder": "k3s-server-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "k3s_server_user",
|
||||
"type": "str",
|
||||
"title": "Server SSH User",
|
||||
"required": false,
|
||||
"default": "ubuntu",
|
||||
"description": "SSH user used by k3sup for the first server",
|
||||
"config": {
|
||||
"placeholder": "ubuntu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "k3s_server_ssh_port",
|
||||
"type": "int",
|
||||
"title": "Server SSH Port",
|
||||
"required": false,
|
||||
"default": 22,
|
||||
"description": "SSH port used for the first server",
|
||||
"config": {
|
||||
"slider": true,
|
||||
"min": 1,
|
||||
"max": 65535,
|
||||
"step": 1,
|
||||
"placeholder": "22"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "k3s_server_ssh_key_path",
|
||||
"type": "str",
|
||||
"title": "SSH Key Path",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Optional private SSH key path for k3sup",
|
||||
"config": {
|
||||
"placeholder": "~/.ssh/id_ed25519"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "k3s_context_name",
|
||||
"type": "str",
|
||||
"title": "Cluster Context",
|
||||
"required": false,
|
||||
"default": "homelab-k3s",
|
||||
"description": "kubectl context name written into the generated kubeconfig",
|
||||
"config": {
|
||||
"placeholder": "homelab-k3s"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "k3sup_use_sudo",
|
||||
"type": "bool",
|
||||
"title": "Use sudo",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Run k3sup remote operations with sudo on the target hosts"
|
||||
},
|
||||
{
|
||||
"name": "extra_server_args",
|
||||
"type": "str",
|
||||
"title": "Server Args",
|
||||
"required": false,
|
||||
"default": "--write-kubeconfig-mode 644 --disable servicelb",
|
||||
"description": "Optional extra K3s server arguments passed through k3sup",
|
||||
"config": {
|
||||
"placeholder": "--write-kubeconfig-mode 644 --disable servicelb"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Agents",
|
||||
"name": "agents",
|
||||
"items": [
|
||||
{
|
||||
"name": "join_agent_nodes",
|
||||
"type": "bool",
|
||||
"title": "Join Agents",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Join matching inventory hosts to the cluster as K3s agents"
|
||||
},
|
||||
{
|
||||
"name": "k3s_agent_hosts_pattern",
|
||||
"type": "str",
|
||||
"title": "Agent Hosts",
|
||||
"required": false,
|
||||
"default": "k3s_agents",
|
||||
"description": "Inventory host pattern used to discover K3s agent nodes",
|
||||
"needs": [
|
||||
"join_agent_nodes=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "k3s_agents"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "k3s_agent_user",
|
||||
"type": "str",
|
||||
"title": "Agent SSH User",
|
||||
"required": false,
|
||||
"default": "ubuntu",
|
||||
"description": "SSH user used by k3sup for agent nodes",
|
||||
"needs": [
|
||||
"join_agent_nodes=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "ubuntu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "k3s_agent_ssh_port",
|
||||
"type": "int",
|
||||
"title": "Agent SSH Port",
|
||||
"required": false,
|
||||
"default": 22,
|
||||
"description": "SSH port used for agent nodes",
|
||||
"needs": [
|
||||
"join_agent_nodes=true"
|
||||
],
|
||||
"config": {
|
||||
"slider": true,
|
||||
"min": 1,
|
||||
"max": 65535,
|
||||
"step": 1,
|
||||
"placeholder": "22"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "extra_agent_args",
|
||||
"type": "str",
|
||||
"title": "Agent Args",
|
||||
"required": false,
|
||||
"default": "",
|
||||
"description": "Optional extra K3s agent arguments passed through k3sup",
|
||||
"needs": [
|
||||
"join_agent_nodes=true"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "--node-label role=worker"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Kubeconfig",
|
||||
"name": "kubeconfig",
|
||||
"items": [
|
||||
{
|
||||
"name": "kubeconfig_destination_mode",
|
||||
"type": "enum",
|
||||
"title": "Destination Mode",
|
||||
"required": false,
|
||||
"default": "local",
|
||||
"description": "Store the generated kubeconfig locally on the control machine or copy it to another managed host",
|
||||
"config": {
|
||||
"options": [
|
||||
"local",
|
||||
"remote"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "local_kubeconfig_path",
|
||||
"type": "str",
|
||||
"title": "Local Kubeconfig",
|
||||
"required": false,
|
||||
"default": "{{ lookup('env', 'HOME') }}/.kube/config",
|
||||
"description": "Local kubeconfig path used when destination mode is local",
|
||||
"needs": [
|
||||
"kubeconfig_destination_mode=local"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "{{ lookup('env', 'HOME') }}/.kube/config"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kubeconfig_remote_host",
|
||||
"type": "str",
|
||||
"title": "Remote Host",
|
||||
"required": false,
|
||||
"default": "localhost",
|
||||
"description": "Managed inventory host that should receive the kubeconfig copy",
|
||||
"needs": [
|
||||
"kubeconfig_destination_mode=remote"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "bastion-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kubeconfig_remote_path",
|
||||
"type": "str",
|
||||
"title": "Remote Kubeconfig",
|
||||
"required": false,
|
||||
"default": "/home/ubuntu/.kube/config",
|
||||
"description": "Destination path on the remote host for the kubeconfig file",
|
||||
"needs": [
|
||||
"kubeconfig_destination_mode=remote"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "/home/ubuntu/.kube/config"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kubeconfig_remote_owner",
|
||||
"type": "str",
|
||||
"title": "Remote Owner",
|
||||
"required": false,
|
||||
"default": "ubuntu",
|
||||
"description": "Owner for the remote kubeconfig file",
|
||||
"needs": [
|
||||
"kubeconfig_destination_mode=remote"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "ubuntu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kubeconfig_remote_group",
|
||||
"type": "str",
|
||||
"title": "Remote Group",
|
||||
"required": false,
|
||||
"default": "ubuntu",
|
||||
"description": "Group for the remote kubeconfig file",
|
||||
"needs": [
|
||||
"kubeconfig_destination_mode=remote"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "ubuntu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kubeconfig_remote_mode",
|
||||
"type": "str",
|
||||
"title": "Remote Mode",
|
||||
"required": false,
|
||||
"default": "0600",
|
||||
"description": "Filesystem mode for the remote kubeconfig file",
|
||||
"needs": [
|
||||
"kubeconfig_destination_mode=remote"
|
||||
],
|
||||
"config": {
|
||||
"placeholder": "0600"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "kubeconfig_remote_become",
|
||||
"type": "bool",
|
||||
"title": "Remote Become",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Use become when creating directories or copying the remote kubeconfig",
|
||||
"needs": [
|
||||
"kubeconfig_destination_mode=remote"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
- name: Add SSH authorized keys
|
||||
hosts: all
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Install SSH public key
|
||||
ansible.posix.authorized_key:
|
||||
user: "<< target_user >>"
|
||||
state: present
|
||||
key: "<< ssh_public_key >>"
|
||||
manage_dir: true
|
||||
|
||||
<%- if configure_passwordless_sudo %>
|
||||
- name: Enable passwordless sudo for the sudo group
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/sudoers.d/90-sudo-nopasswd
|
||||
content: |
|
||||
%sudo ALL=(ALL:ALL) NOPASSWD: ALL
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
<%- endif %>
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"slug": "ubuntu-add-sshkey",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Add SSH Key and Configure Sudoers",
|
||||
"description": "Installs a single SSH public key for a target user on the workload-selected hosts and can enable passwordless sudo for the Ubuntu sudo group.",
|
||||
"tags": [],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "ansible"
|
||||
},
|
||||
"draft": false,
|
||||
"version": {
|
||||
"name": "1.0.0",
|
||||
"source_dep_name": "manual/ubuntu-add-sshkey"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Access",
|
||||
"name": "access",
|
||||
"items": [
|
||||
{
|
||||
"name": "target_user",
|
||||
"type": "str",
|
||||
"title": "Target User",
|
||||
"required": false,
|
||||
"default": "ubuntu",
|
||||
"description": "Linux user account that should receive the authorized SSH keys.",
|
||||
"config": {
|
||||
"placeholder": "ubuntu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssh_public_key",
|
||||
"type": "str",
|
||||
"title": "SSH Public Key",
|
||||
"required": true,
|
||||
"description": "SSH public key to add to the target user's authorized_keys file.",
|
||||
"config": {
|
||||
"textarea": true,
|
||||
"placeholder": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBExampleKey user@laptop"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "configure_passwordless_sudo",
|
||||
"type": "bool",
|
||||
"title": "Passwordless Sudo",
|
||||
"required": false,
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
- name: Install curated Ubuntu packages
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
vars:
|
||||
ubuntu_package_matrix:
|
||||
- name: python3-venv
|
||||
enabled: << install_python3_venv >>
|
||||
- name: python3-pip
|
||||
enabled: << install_python3_pip >>
|
||||
- name: python3-apt
|
||||
enabled: << install_python3_apt >>
|
||||
- name: curl
|
||||
enabled: << install_curl >>
|
||||
- name: wget
|
||||
enabled: << install_wget >>
|
||||
- name: git
|
||||
enabled: << install_git >>
|
||||
- name: jq
|
||||
enabled: << install_jq >>
|
||||
- name: rsync
|
||||
enabled: << install_rsync >>
|
||||
- name: unzip
|
||||
enabled: << install_unzip >>
|
||||
- name: ca-certificates
|
||||
enabled: << install_ca_certificates >>
|
||||
- name: gnupg
|
||||
enabled: << install_gnupg >>
|
||||
- name: lsb-release
|
||||
enabled: << install_lsb_release >>
|
||||
- name: prometheus-node-exporter
|
||||
enabled: << install_prometheus_node_exporter >>
|
||||
- name: nfs-common
|
||||
enabled: << install_nfs_common >>
|
||||
- name: qemu-guest-agent
|
||||
enabled: << install_qemu_guest_agent >>
|
||||
|
||||
tasks:
|
||||
- name: Validate Ubuntu APT target
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['pkg_mgr'] == 'apt'
|
||||
fail_msg: "This playbook only supports Ubuntu hosts that use APT."
|
||||
|
||||
- name: Build selected package list
|
||||
ansible.builtin.set_fact:
|
||||
ubuntu_selected_packages: "{{ ubuntu_package_matrix | selectattr('enabled') | map(attribute='name') | list }}"
|
||||
|
||||
- name: Refresh APT package index
|
||||
ansible.builtin.command:
|
||||
cmd: apt-get update
|
||||
environment:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
register: ubuntu_packages_apt_update
|
||||
changed_when: false
|
||||
|
||||
- name: Install selected Ubuntu packages
|
||||
ansible.builtin.apt:
|
||||
name: "{{ ubuntu_selected_packages }}"
|
||||
state: present
|
||||
update_cache: false
|
||||
when: ubuntu_selected_packages | length > 0
|
||||
|
||||
- name: Enable and start Prometheus node exporter
|
||||
ansible.builtin.service:
|
||||
name: prometheus-node-exporter
|
||||
state: started
|
||||
enabled: true
|
||||
when: << install_prometheus_node_exporter >>
|
||||
|
||||
- name: Enable and start QEMU guest agent
|
||||
ansible.builtin.service:
|
||||
name: qemu-guest-agent
|
||||
state: started
|
||||
enabled: true
|
||||
when: << install_qemu_guest_agent >>
|
||||
|
||||
- name: Show selected package summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Host: {{ inventory_hostname }}"
|
||||
- "Selected packages: {{ ubuntu_selected_packages | join(', ') if (ubuntu_selected_packages | length > 0) else 'none' }}"
|
||||
@@ -1,157 +0,0 @@
|
||||
{
|
||||
"slug": "ubuntu-packages",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Install Ubuntu Packages",
|
||||
"description": "Installs a curated set of useful Ubuntu packages with per-package switches for scripting, package management, monitoring, NFS, and VM guest integration.",
|
||||
"tags": [
|
||||
"ubuntu",
|
||||
"apt",
|
||||
"packages",
|
||||
"ansible"
|
||||
],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "ansible"
|
||||
},
|
||||
"draft": false,
|
||||
"version": {
|
||||
"name": "1.0.0",
|
||||
"source_dep_name": "manual/ubuntu-packages"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"title": "Base Packages",
|
||||
"name": "base_packages",
|
||||
"items": [
|
||||
{
|
||||
"name": "install_python3_venv",
|
||||
"type": "bool",
|
||||
"title": "python3-venv",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install Python virtual environment support."
|
||||
},
|
||||
{
|
||||
"name": "install_python3_pip",
|
||||
"type": "bool",
|
||||
"title": "python3-pip",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install pip for Python 3."
|
||||
},
|
||||
{
|
||||
"name": "install_python3_apt",
|
||||
"type": "bool",
|
||||
"title": "python3-apt",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install Python bindings for APT."
|
||||
},
|
||||
{
|
||||
"name": "install_curl",
|
||||
"type": "bool",
|
||||
"title": "curl",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install curl for HTTP requests and downloads."
|
||||
},
|
||||
{
|
||||
"name": "install_wget",
|
||||
"type": "bool",
|
||||
"title": "wget",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install wget for scripted downloads."
|
||||
},
|
||||
{
|
||||
"name": "install_git",
|
||||
"type": "bool",
|
||||
"title": "git",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install Git for source control and repository operations."
|
||||
},
|
||||
{
|
||||
"name": "install_jq",
|
||||
"type": "bool",
|
||||
"title": "jq",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install jq for JSON parsing in shell scripts."
|
||||
},
|
||||
{
|
||||
"name": "install_rsync",
|
||||
"type": "bool",
|
||||
"title": "rsync",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install rsync for file synchronization and copy jobs."
|
||||
},
|
||||
{
|
||||
"name": "install_unzip",
|
||||
"type": "bool",
|
||||
"title": "unzip",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install unzip for archive extraction."
|
||||
},
|
||||
{
|
||||
"name": "install_ca_certificates",
|
||||
"type": "bool",
|
||||
"title": "ca-certificates",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install CA certificates for HTTPS trust."
|
||||
},
|
||||
{
|
||||
"name": "install_gnupg",
|
||||
"type": "bool",
|
||||
"title": "gnupg",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install GnuPG for repository keys and signing workflows."
|
||||
},
|
||||
{
|
||||
"name": "install_lsb_release",
|
||||
"type": "bool",
|
||||
"title": "lsb-release",
|
||||
"required": false,
|
||||
"default": true,
|
||||
"description": "Install lsb-release for distro detection in scripts."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Optional Packages",
|
||||
"name": "optional_packages",
|
||||
"items": [
|
||||
{
|
||||
"name": "install_prometheus_node_exporter",
|
||||
"type": "bool",
|
||||
"title": "prometheus-node-exporter",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Install Prometheus node exporter and enable its service."
|
||||
},
|
||||
{
|
||||
"name": "install_nfs_common",
|
||||
"type": "bool",
|
||||
"title": "nfs-common",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Install NFS client utilities."
|
||||
},
|
||||
{
|
||||
"name": "install_qemu_guest_agent",
|
||||
"type": "bool",
|
||||
"title": "qemu-guest-agent",
|
||||
"required": false,
|
||||
"default": false,
|
||||
"description": "Install the QEMU guest agent and enable its service."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
- name: Install Ubuntu VM core packages
|
||||
hosts: all
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Install packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- prometheus-node-exporter
|
||||
- nfs-common
|
||||
- qemu-guest-agent
|
||||
update_cache: true
|
||||
|
||||
- name: Start guest qemu-guest-agent
|
||||
ansible.builtin.service:
|
||||
name: qemu-guest-agent
|
||||
state: started
|
||||
enabled: true
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"slug": "ubuntu-vm-core",
|
||||
"kind": "ansible",
|
||||
"metadata": {
|
||||
"name": "Install Ubuntu VM Core Packages",
|
||||
"description": "Installs essential packages for Ubuntu virtual machines, including Prometheus node exporter, NFS client utilities, and QEMU guest agent.",
|
||||
"tags": [],
|
||||
"icon": {
|
||||
"provider": "simple-icons",
|
||||
"id": "ansible"
|
||||
},
|
||||
"draft": false,
|
||||
"version": {
|
||||
"name": "1.0.0",
|
||||
"source_dep_name": "manual/ubuntu-vm-core"
|
||||
}
|
||||
},
|
||||
"variables": []
|
||||
}
|
||||
Reference in New Issue
Block a user