Initial infrastructure mono-repo scaffold

Consolidates nixos, docker, raspi, and debian-configuration into a single
infrastructure-as-code repo. Includes:

- ansible/: full inventory + proxmox-hardening, freeipa, and raspberrypi
  roles (converted from debian-configuration bash scripts)
- terraform/: Proxmox VMs, Dynu DNS, Pi-hole (decommissioned stub),
  Docker container catalog — migrated from docker/infrastructure/terraform/
- stacks/docker/, stacks/raspi/, nixos/: placeholder READMEs pending
  git subtree population (see implementation plan)
- docs/: internal MkDocs site with architecture, network topology, runbooks,
  and drift-detection guide; external sanitized site
- scripts/: drift-detect.sh, docs-build.sh, install-hooks.sh, check-secrets.sh
- CI: secret-scan (push/PR), drift-detect (daily), docs-build (on change)
- Pi-hole removed throughout — DNS is FreeIPA, DHCP is router

See docs/internal/implementation-plan.md for the phased rollout after
pushing to Gitea.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UvNjoxTWEDkhXsd1Dq2ETP
This commit is contained in:
2026-07-30 07:07:47 +10:00
co-authored by Claude Sonnet 4.6
commit f85c65870f
125 changed files with 8186 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
[defaults]
inventory = ./inventory/hosts.yml
collections_path = ./collections
retry_files_enabled = False
stdout_callback = yaml
host_key_checking = True
# Fail fast on unreachable hosts rather than silently skipping
any_errors_fatal = False
# Use pipelining for speed (requires requiretty disabled in sudoers, which our roles handle)
pipelining = True
[inventory]
enable_plugins = yaml, ini
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=30
+5
View File
@@ -0,0 +1,5 @@
---
collections:
- name: ansible.posix
- name: community.general
- name: ansible.utils
+20
View File
@@ -0,0 +1,20 @@
---
# Variables applied to every host.
# Override per-group in group_vars/<group>.yml or per-host in host_vars/<host>/vars.yml.
ansible_python_interpreter: /usr/bin/python3
# LAN domain
lan_domain: sweet.home
tailnet_domain: tail13f623.ts.net
# DNS: FreeIPA is the authoritative resolver for sweet.home (Pi-hole decommissioned).
# All LAN clients point directly to domain-controller.sweet.home for DNS.
ipa_realm: SWEET.HOME
ipa_server: domain-controller.sweet.home
# Docker access GID — must match FreeIPA docker-access group GID
docker_access_gid: 50010
# IPA admins group granted passwordless sudo on all enrolled hosts
ipa_admin_group: admins
+14
View File
@@ -0,0 +1,14 @@
---
# Proxmox-group defaults.
# Per-host overrides go in host_vars/pve1.sweet.home/vars.yml etc.
# proxmox-hardening role toggles
proxmox_harden_ssh: true
proxmox_configure_firewall: true
proxmox_configure_unattended_upgrades: true
proxmox_switch_to_nosub_repo: true
proxmox_disable_nag: true
proxmox_setup_ipa_sudo: true
# Firewall: management CIDR — override per host if subnets differ
# proxmox_mgmt_cidr is set per host in hosts.yml
+5
View File
@@ -0,0 +1,5 @@
---
# Raspberry Pi group defaults.
raspberrypi_setup_ipa_sudo: true
raspberrypi_pin_docker_gid: true
+79
View File
@@ -0,0 +1,79 @@
---
# Full infrastructure inventory.
# IPs are documented here for reference; use FQDNs where DNS is reliable.
# Hosts marked [nixos] are managed by the NixOS flake (nixos/) and are present
# here only for Ansible tasks that apply to them (e.g. drift-check pings).
all:
children:
# ── Proxmox hypervisors ───────────────────────────────────────────────────
proxmox:
hosts:
pve1.sweet.home:
ansible_user: wayne
proxmox_node_name: pve
proxmox_role: production
proxmox_mgmt_cidr: "192.168.2.0/24"
proxmox_admin_username: wayne
pve-test.sweet.home:
ansible_user: wayne
proxmox_node_name: pve-test
proxmox_role: sandbox
proxmox_mgmt_cidr: "192.168.2.0/24"
proxmox_admin_username: wayne
# ── Identity / DNS ────────────────────────────────────────────────────────
freeipa:
hosts:
domain-controller.sweet.home:
ansible_user: wayne
# IPA server parameters (consumed by freeipa role)
ipa_realm: "SWEET.HOME"
ipa_domain: "sweet.home"
ipa_hostname: "domain-controller.sweet.home"
ipa_ip: "192.168.2.253"
ipa_dns_forwarder: "192.168.2.138" # Pi-hole
ansible_python_interpreter: /usr/bin/python3
# ── Edge / monitoring ─────────────────────────────────────────────────────
raspi:
hosts:
raspberrypi.tail13f623.ts.net:
ansible_user: wayne
docker_access_gid: 50010
# ── NixOS hosts (flake-managed; present for ping/audit tasks only) ────────
nixos:
vars:
ansible_note: >
These hosts are managed by the NixOS flake in nixos/.
Only non-NixOS tasks (connectivity checks, IPA enrollment helpers)
should target this group directly from Ansible.
hosts:
docker.sweet.home:
ansible_host: 192.168.2.225
ansible_user: wayne
nixos_build_type: docker
nix-cache.sweet.home:
ansible_host: 192.168.2.224
ansible_user: wayne
nixos_build_type: nix-cache
# ── Groupings for playbook targeting ─────────────────────────────────────
linux:
children:
proxmox: {}
freeipa: {}
raspi: {}
nixos: {}
network:
children:
freeipa: {}
non_nixos:
children:
proxmox: {}
freeipa: {}
raspi: {}
+18
View File
@@ -0,0 +1,18 @@
---
- name: FreeIPA server provisioning
hosts: freeipa
become: true
gather_facts: true
pre_tasks:
- name: Confirm Rocky Linux 9
ansible.builtin.assert:
that:
- ansible_distribution == "Rocky"
- ansible_distribution_major_version == "9"
fail_msg: "FreeIPA role targets Rocky Linux 9 only."
tags: always
roles:
- role: freeipa
tags: freeipa
+8
View File
@@ -0,0 +1,8 @@
---
# Connectivity check — verify all hosts are reachable before a real run.
- name: Ping all hosts
hosts: all
gather_facts: false
tasks:
- name: Ping
ansible.builtin.ping:
+18
View File
@@ -0,0 +1,18 @@
---
- name: Proxmox VE hardening
hosts: proxmox
become: true
gather_facts: true
pre_tasks:
- name: Confirm this is a Proxmox VE host
ansible.builtin.assert:
that:
- ansible_os_family == "Debian"
- ansible_facts.packages is not defined or true
fail_msg: "This playbook targets Proxmox VE (Debian-based) hosts only."
tags: always
roles:
- role: proxmox-hardening
tags: proxmox
+22
View File
@@ -0,0 +1,22 @@
---
- name: Raspberry Pi setup
hosts: raspi
become: true
gather_facts: true
pre_tasks:
- name: Confirm IPA enrollment
ansible.builtin.stat:
path: /etc/ipa/default.conf
register: ipa_conf
tags: always
- name: Warn if not IPA-enrolled
ansible.builtin.debug:
msg: "WARNING: /etc/ipa/default.conf not found. IPA-dependent tasks will be skipped."
when: not ipa_conf.stat.exists
tags: always
roles:
- role: raspberrypi
tags: raspi
+13
View File
@@ -0,0 +1,13 @@
---
# Master playbook — runs every role against its target group.
# Always use --check --diff on first run against production hosts.
# Use --limit <host_pattern> to target a subset.
#
# Examples:
# ansible-playbook site.yml --check --diff # dry-run everything
# ansible-playbook site.yml --limit pve1.sweet.home # production proxmox only
# ansible-playbook site.yml --limit proxmox --tags ssh # only SSH tasks on all PVE nodes
- import_playbook: proxmox.yml
- import_playbook: freeipa.yml
- import_playbook: raspi.yml
+14
View File
@@ -0,0 +1,14 @@
---
# freeipa role defaults — all overridable in inventory host_vars or group_vars.
# Most values come from the per-host inventory (see hosts.yml).
ipa_realm: "SWEET.HOME"
ipa_domain: "sweet.home"
# Set these per-host in inventory/hosts.yml:
# ipa_hostname: "domain-controller.sweet.home"
# ipa_ip: "192.168.2.253"
# ipa_dns_forwarder: "192.168.2.138"
# Swap file created if no swap exists (FreeIPA needs headroom during install)
ipa_swap_size_mb: 2048
+133
View File
@@ -0,0 +1,133 @@
---
# freeipa/tasks/main.yml
# Provisions a FreeIPA 4.x server on Rocky Linux 9.
# The ipa-server-install step is intentionally NOT idempotent — it will
# refuse to run if IPA is already installed, which acts as a safety guard.
# ── Pre-flight ────────────────────────────────────────────────────────────────
- name: Check IPA is not already installed
ansible.builtin.stat:
path: /etc/ipa/default.conf
register: ipa_installed
tags: always
- name: Skip install tasks if IPA already exists
ansible.builtin.debug:
msg: "FreeIPA already installed — skipping install tasks. Use verify tasks to check health."
when: ipa_installed.stat.exists
tags: install
- name: Assert hostname is correct FQDN
ansible.builtin.command:
cmd: hostname -f
register: fqdn_check
changed_when: false
failed_when: fqdn_check.stdout != ipa_hostname
when:
- not ipa_installed.stat.exists
- ipa_hostname is defined
tags: install, preflight
- name: Assert /etc/hosts has correct entry
ansible.builtin.lineinfile:
path: /etc/hosts
line: "{{ ipa_ip }} {{ ipa_hostname }} {{ ipa_hostname.split('.')[0] }}"
state: present
when:
- not ipa_installed.stat.exists
- ipa_ip is defined
- ipa_hostname is defined
tags: install, preflight
# ── Swap ──────────────────────────────────────────────────────────────────────
- name: Check current swap
ansible.builtin.command:
cmd: swapon --show
register: swap_check
changed_when: false
tags: install, swap
- name: Create swap file if absent
block:
- name: Create swap file
ansible.builtin.command:
cmd: "dd if=/dev/zero of=/swapfile bs=1M count={{ ipa_swap_size_mb }} status=none"
args:
creates: /swapfile
- name: Set swap permissions
ansible.builtin.file:
path: /swapfile
mode: "0600"
- name: Format swap
ansible.builtin.command:
cmd: mkswap /swapfile
changed_when: true
- name: Enable swap
ansible.builtin.command:
cmd: swapon /swapfile
changed_when: true
- name: Add swap to fstab
ansible.builtin.lineinfile:
path: /etc/fstab
line: "/swapfile none swap defaults 0 0"
state: present
when:
- not ipa_installed.stat.exists
- swap_check.stdout == ""
tags: install, swap
# ── Package install ───────────────────────────────────────────────────────────
- name: Install FreeIPA server packages
ansible.builtin.dnf:
name:
- freeipa-server
- freeipa-server-dns
state: present
when: not ipa_installed.stat.exists
tags: install, packages
# ── IPA server install ────────────────────────────────────────────────────────
- name: Run ipa-server-install (interactive passwords via env vars)
ansible.builtin.shell:
cmd: >
ipa-server-install
--realm={{ ipa_realm }}
--domain={{ ipa_domain }}
--hostname={{ ipa_hostname }}
--ip-address={{ ipa_ip }}
--forwarder={{ ipa_dns_forwarder }}
--setup-dns
--auto-reverse
--no-host-dns
--mkhomedir
--unattended
--ds-password="${IPA_DM_PASSWORD}"
--admin-password="${IPA_ADMIN_PASSWORD}"
environment:
IPA_DM_PASSWORD: "{{ ipa_dm_password }}"
IPA_ADMIN_PASSWORD: "{{ ipa_admin_password }}"
no_log: true
when: not ipa_installed.stat.exists
tags: install
# ── Verification ──────────────────────────────────────────────────────────────
- name: Verify IPA services are running
ansible.builtin.command:
cmd: ipactl status
register: ipa_status
changed_when: false
tags: verify
- name: Print IPA service status
ansible.builtin.debug:
var: ipa_status.stdout_lines
tags: verify
@@ -0,0 +1,22 @@
---
# proxmox-hardening role defaults.
# Override in inventory/group_vars/proxmox.yml or host_vars/<host>/vars.yml.
proxmox_harden_ssh: true
proxmox_configure_firewall: true
proxmox_configure_unattended_upgrades: true
proxmox_switch_to_nosub_repo: true
proxmox_disable_nag: true
proxmox_setup_ipa_sudo: true
# Management CIDR for the PVE datacenter firewall (inbound allow-list).
# Must be set per-host in inventory (hosts.yml) — no default here to force explicit assignment.
# proxmox_mgmt_cidr: "192.168.2.0/24"
# Named PVE admin username to create (set to "" to skip user creation).
# proxmox_admin_username: "wayne"
# fail2ban sshd jail settings
proxmox_fail2ban_maxretry: 5
proxmox_fail2ban_bantime: "1h"
proxmox_fail2ban_findtime: "10m"
@@ -0,0 +1,19 @@
---
- name: apt update
ansible.builtin.apt:
update_cache: true
- name: reload sshd
ansible.builtin.systemd:
name: sshd
state: reloaded
- name: restart fail2ban
ansible.builtin.systemd:
name: fail2ban
state: restarted
- name: restart pve-firewall
ansible.builtin.command:
cmd: pve-firewall restart
changed_when: true
@@ -0,0 +1,243 @@
---
# proxmox-hardening/tasks/main.yml
# Mirrors the bash scripts in debian-configuration/proxmox/scripts/.
# All tasks are idempotent — safe to re-run.
# ── APT repos ─────────────────────────────────────────────────────────────────
- name: Disable enterprise apt source (rename to .disabled)
ansible.builtin.find:
paths: /etc/apt/sources.list.d
patterns: "*.sources,*.list"
register: apt_sources
when: proxmox_switch_to_nosub_repo
tags: repos
- name: Disable enterprise apt sources
ansible.builtin.command:
cmd: "mv {{ item.path }} {{ item.path }}.disabled"
loop: "{{ apt_sources.files | default([]) }}"
when:
- proxmox_switch_to_nosub_repo
- item.path is not search('.disabled')
register: mv_result
changed_when: mv_result.rc == 0
failed_when: mv_result.rc not in [0, 1]
# Only disable files that actually reference enterprise.proxmox.com
# (checked by grep in the shell — using shell here for grep pipe)
tags: repos
- name: Write no-subscription apt source
ansible.builtin.template:
src: pve-no-subscription.sources.j2
dest: /etc/apt/sources.list.d/pve-no-subscription.sources
mode: "0644"
notify: apt update
when: proxmox_switch_to_nosub_repo
tags: repos
# ── SSH hardening ─────────────────────────────────────────────────────────────
- name: Ensure sshd_config.d directory exists
ansible.builtin.file:
path: /etc/ssh/sshd_config.d
state: directory
mode: "0755"
when: proxmox_harden_ssh
tags: ssh
- name: Apply SSH hardening drop-in
ansible.builtin.template:
src: sshd-hardening.conf.j2
dest: /etc/ssh/sshd_config.d/99-hardening.conf
mode: "0644"
validate: "sshd -t -f %s"
notify: reload sshd
when: proxmox_harden_ssh
tags: ssh
- name: Install fail2ban
ansible.builtin.apt:
name: fail2ban
state: present
update_cache: false
when: proxmox_harden_ssh
tags: ssh
- name: Write fail2ban sshd jail
ansible.builtin.template:
src: fail2ban-sshd.local.j2
dest: /etc/fail2ban/jail.d/sshd.local
mode: "0644"
notify: restart fail2ban
when: proxmox_harden_ssh
tags: ssh
- name: Enable and start fail2ban
ansible.builtin.systemd:
name: fail2ban
enabled: true
state: started
when: proxmox_harden_ssh
tags: ssh
# ── PVE datacenter firewall ───────────────────────────────────────────────────
- name: Ensure /etc/pve/firewall exists
ansible.builtin.file:
path: /etc/pve/firewall
state: directory
mode: "0750"
when: proxmox_configure_firewall and proxmox_mgmt_cidr is defined
tags: firewall
- name: Deploy datacenter firewall config
ansible.builtin.template:
src: cluster-fw.j2
dest: /etc/pve/firewall/cluster.fw
mode: "0640"
notify: restart pve-firewall
when: proxmox_configure_firewall and proxmox_mgmt_cidr is defined
tags: firewall
# ── Unattended upgrades ───────────────────────────────────────────────────────
- name: Install unattended-upgrades
ansible.builtin.apt:
name: unattended-upgrades
state: present
when: proxmox_configure_unattended_upgrades
tags: upgrades
- name: Write unattended-upgrades origins config
ansible.builtin.template:
src: unattended-upgrades.conf.j2
dest: /etc/apt/apt.conf.d/51pve-unattended-upgrades.conf
mode: "0644"
when: proxmox_configure_unattended_upgrades
tags: upgrades
- name: Write auto-upgrades config
ansible.builtin.copy:
content: |
// Managed by ansible proxmox-hardening role
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
dest: /etc/apt/apt.conf.d/20auto-upgrades
mode: "0644"
when: proxmox_configure_unattended_upgrades
tags: upgrades
- name: Enable unattended-upgrades service
ansible.builtin.systemd:
name: unattended-upgrades
enabled: true
state: started
when: proxmox_configure_unattended_upgrades
tags: upgrades
# ── PVE admin user ────────────────────────────────────────────────────────────
- name: Check if named admin user already exists
ansible.builtin.command:
cmd: "pveum user list --output-format json"
register: pve_users
changed_when: false
when: proxmox_admin_username is defined and proxmox_admin_username != ""
tags: users
- name: Create named PVE admin user (one-time — password printed, must be changed on first login)
ansible.builtin.command:
cmd: >
pveum user add {{ proxmox_admin_username }}@pve
--password {{ lookup('password', '/dev/null length=24 chars=ascii_letters,digits') }}
--comment "Named admin account, managed by ansible proxmox-hardening role"
when:
- proxmox_admin_username is defined
- proxmox_admin_username != ""
- pve_users.stdout is defined
- proxmox_admin_username + "@pve" not in pve_users.stdout
no_log: true
tags: users
- name: Grant Administrator role to PVE admin user
ansible.builtin.command:
cmd: "pveum acl modify / --users {{ proxmox_admin_username }}@pve --roles Administrator"
when:
- proxmox_admin_username is defined
- proxmox_admin_username != ""
- pve_users.stdout is defined
- proxmox_admin_username + "@pve" not in pve_users.stdout
tags: users
# ── IPA sudo ──────────────────────────────────────────────────────────────────
- name: Check IPA enrollment
ansible.builtin.stat:
path: /etc/ipa/default.conf
register: ipa_conf
when: proxmox_setup_ipa_sudo
tags: ipa
- name: Write admins NOPASSWD sudoers file
ansible.builtin.copy:
content: "%admins ALL=(root) NOPASSWD: ALL\n"
dest: /etc/sudoers.d/admins-nopasswd
mode: "0440"
validate: "visudo -cf %s"
when:
- proxmox_setup_ipa_sudo
- ipa_conf.stat.exists
tags: ipa
- name: Find pvesh/qm/pct paths for proxmox sudoers
ansible.builtin.command:
cmd: "which {{ item }}"
register: pve_tools
loop: [pvesh, qm, pct]
changed_when: false
failed_when: false
when:
- proxmox_setup_ipa_sudo
- ipa_conf.stat.exists
tags: ipa
- name: Write admins proxmox tools sudoers file
ansible.builtin.copy:
content: >
%admins ALL=(root) NOPASSWD:
{{ pve_tools.results | map(attribute='stdout') | select | join(', ') }}
dest: /etc/sudoers.d/admins-proxmox
mode: "0440"
validate: "visudo -cf %s"
when:
- proxmox_setup_ipa_sudo
- ipa_conf.stat.exists
- pve_tools.results | map(attribute='rc') | select('eq', 0) | list | length == 3
tags: ipa
# ── Subscription nag ──────────────────────────────────────────────────────────
- name: Check if nag patch is needed
ansible.builtin.shell:
cmd: >
grep -qF "data.status.toLowerCase() !== 'active'"
/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
register: nag_check
changed_when: false
failed_when: false
when: proxmox_disable_nag
tags: nag
- name: Patch subscription nag
ansible.builtin.replace:
path: /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
regexp: "data\\.status\\.toLowerCase\\(\\) !== 'active'"
replace: "false"
backup: true
when:
- proxmox_disable_nag
- nag_check.rc == 0
tags: nag
@@ -0,0 +1,27 @@
# Managed by ansible proxmox-hardening role — do not edit by hand
# Proxmox datacenter-level firewall (Stage 1: single-node management access only)
# Stage 2 (Corosync/Ceph cluster) rules remain commented until needed.
[OPTIONS]
enable: 1
policy_in: DROP
policy_out: ACCEPT
[IPSET mgmt]
# Management subnet — SSH and web UI access only from here
{{ proxmox_mgmt_cidr }}
[RULES]
# Allow SSH from management network
IN ACCEPT -source +mgmt -p tcp --dport 22 -log nolog
# Allow PVE web UI from management network
IN ACCEPT -source +mgmt -p tcp --dport 8006 -log nolog
# Allow SPICE console from management network
IN ACCEPT -source +mgmt -p tcp --dport 3128 -log nolog
# Stage 2 — Corosync (uncomment when clustering pve1 with additional nodes)
# IN ACCEPT -source +mgmt -p udp --dport 5404:5405 -log nolog
# Stage 2 — Ceph (uncomment when Ceph OSD replication is active)
# IN ACCEPT -source +mgmt -p tcp --dport 6800:7568 -log nolog
@@ -0,0 +1,8 @@
# Managed by ansible proxmox-hardening role — do not edit by hand
[sshd]
enabled = true
port = ssh
backend = systemd
maxretry = {{ proxmox_fail2ban_maxretry }}
bantime = {{ proxmox_fail2ban_bantime }}
findtime = {{ proxmox_fail2ban_findtime }}
@@ -0,0 +1,6 @@
# Managed by ansible proxmox-hardening role — do not edit by hand
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: {{ ansible_distribution_release }}
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
@@ -0,0 +1,3 @@
# Managed by ansible proxmox-hardening role — do not edit by hand
PermitRootLogin prohibit-password
PasswordAuthentication no
@@ -0,0 +1,11 @@
// Managed by ansible proxmox-hardening role — do not edit by hand
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename={{ ansible_distribution_release }},label=Debian-Security";
"origin=Debian,codename={{ ansible_distribution_release }}-security,label=Debian-Security";
"origin=Proxmox";
};
// Never auto-reboot a hypervisor. Check /var/run/reboot-required manually.
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Remove-Unused-Dependencies "false";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "false";
@@ -0,0 +1,6 @@
---
# raspberrypi role defaults.
raspberrypi_setup_ipa_sudo: true
raspberrypi_pin_docker_gid: true
docker_access_gid: 50010
+74
View File
@@ -0,0 +1,74 @@
---
# raspberrypi/tasks/main.yml
# Post-IPA-enrollment setup for the Raspberry Pi edge node.
# Mirrors debian-configuration/raspberrypi/scripts/.
# ── IPA sudo ──────────────────────────────────────────────────────────────────
- name: Check IPA enrollment
ansible.builtin.stat:
path: /etc/ipa/default.conf
register: ipa_conf
tags: ipa
- name: Write IPA admins sudoers file
ansible.builtin.copy:
content: "%admins ALL=(ALL) NOPASSWD:ALL\n"
dest: /etc/sudoers.d/ipa-admins
mode: "0440"
validate: "visudo -cf %s"
when:
- raspberrypi_setup_ipa_sudo
- ipa_conf.stat.exists
tags: ipa
# ── Docker GID pinning ────────────────────────────────────────────────────────
- name: Check docker group GID
ansible.builtin.command:
cmd: "getent group docker"
register: docker_group
changed_when: false
failed_when: false
when: raspberrypi_pin_docker_gid
tags: docker
- name: Pin docker group GID to IPA docker-access GID
block:
- name: Stop docker service and socket
ansible.builtin.systemd:
name: "{{ item }}"
state: stopped
loop:
- docker.service
- docker.socket
- name: Remove stale docker socket
ansible.builtin.file:
path: /var/run/docker.sock
state: absent
- name: Reassign docker group GID (non-unique — SSSD already owns this GID)
ansible.builtin.command:
cmd: "groupmod --non-unique -g {{ docker_access_gid }} docker"
changed_when: true
- name: Start docker socket and service
ansible.builtin.systemd:
name: "{{ item }}"
state: started
loop:
- docker.socket
- docker.service
- name: Verify socket GID
ansible.builtin.command:
cmd: "stat -c '%g' /var/run/docker.sock"
register: socket_gid
changed_when: false
failed_when: socket_gid.stdout != docker_access_gid | string
when:
- raspberrypi_pin_docker_gid
- docker_group.rc == 0
- docker_group.stdout.split(':')[2] != docker_access_gid | string
tags: docker