Compare commits
56 Commits
09b3ed44ba
...
07492b5b57
| Author | SHA1 | Date | |
|---|---|---|---|
| 07492b5b57 | |||
| 14913bcd3d | |||
| 041650c287 | |||
| a63ffbc731 | |||
| 9d2f1cc5bd | |||
| f72f9feb9a | |||
| 417737f904 | |||
| a06c2ebdcf | |||
| e174ecda42 | |||
| 5246a905bb | |||
| d00d84b69c | |||
| 4dafa8c596 | |||
| 53584b8730 | |||
| ce40468b77 | |||
| 4b4fab3c33 | |||
| db2fab5e7d | |||
| 42be0a5919 | |||
| 17400fa6ff | |||
| deb14d2c94 | |||
| 65c5b1029b | |||
| a1fbb7c21d | |||
| d076ac8fef | |||
| c82e4afc4d | |||
| ac72fdc4a6 | |||
| b2e050c467 | |||
| 914d7dd9d1 | |||
| 21bf8f79e2 | |||
| 38feff4369 | |||
| 404529e8a4 | |||
| 3db18858c3 | |||
| 72a9576abe | |||
| 462c2c7dfe | |||
| ef8bfeaf84 | |||
| ba6be037ac | |||
| 5ca1c7f570 | |||
| cd8e477534 | |||
| c439e9741e | |||
| 0a5c70e49f | |||
| 19f2c9efe2 | |||
| 230c74fd9b | |||
| a2c19e2e49 | |||
| 9f9a4b38b8 | |||
| 524356cf8d | |||
| a2993212ca | |||
| fba2e5fc94 | |||
| cf68a93b45 | |||
| 3000268a0e | |||
| 196c5be67a | |||
| 33bad193b4 | |||
| d5277802f7 | |||
| 28e6cf50d1 | |||
| 42cb5071c2 | |||
| 23a798a63a | |||
| 5dd84c6b39 | |||
| d0ae20911b | |||
| b6d06dd96d |
19
.yamllint
Normal file
19
.yamllint
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
document-start: disable
|
||||
line-length:
|
||||
max: 200
|
||||
allow-non-breakable-words: true
|
||||
allow-non-breakable-inline-mappings: true
|
||||
truthy:
|
||||
allowed-values: ["true", "false"]
|
||||
check-keys: false
|
||||
comments:
|
||||
min-spaces-from-content: 1
|
||||
comments-indentation: disable
|
||||
braces:
|
||||
max-spaces-inside: 1
|
||||
octal-values:
|
||||
forbid-implicit-octal: true
|
||||
@@ -1,2 +1,5 @@
|
||||
[defaults]
|
||||
hash_behaviour = merge
|
||||
interpreter_python = auto_silent
|
||||
deprecation_warnings = False
|
||||
host_key_checking = False
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
register: bootstrap_almalinux_base_result
|
||||
changed_when: bootstrap_almalinux_base_result.rc == 0
|
||||
|
||||
- name: Ensure chroot has resolv.conf
|
||||
ansible.builtin.file:
|
||||
src: /run/NetworkManager/resolv.conf
|
||||
dest: /mnt/etc/resolv.conf
|
||||
state: link
|
||||
|
||||
- name: Install extra packages
|
||||
ansible.builtin.command: >-
|
||||
{{ chroot_command }} dnf --releasever={{ os_version }} --setopt=install_weak_deps=False
|
||||
|
||||
@@ -6,13 +6,6 @@
|
||||
lookup('vars', bootstrap_var_key) | reject('equalto', '') | join(' ')
|
||||
}}
|
||||
block:
|
||||
- name: Ensure chroot has resolv.conf
|
||||
ansible.builtin.file:
|
||||
src: /run/NetworkManager/resolv.conf
|
||||
dest: /mnt/etc/resolv.conf
|
||||
state: link
|
||||
force: true
|
||||
|
||||
- name: Install Alpine Linux packages
|
||||
ansible.builtin.command: >
|
||||
apk --root /mnt --no-cache add alpine-base
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
register: bootstrap_fedora_base_result
|
||||
changed_when: bootstrap_fedora_base_result.rc == 0
|
||||
|
||||
- name: Ensure chroot has resolv.conf
|
||||
ansible.builtin.file:
|
||||
src: /run/NetworkManager/resolv.conf
|
||||
dest: /mnt/etc/resolv.conf
|
||||
state: link
|
||||
|
||||
- name: Install extra packages
|
||||
ansible.builtin.command: >-
|
||||
{{ chroot_command }} dnf --releasever={{ os_version }} --setopt=install_weak_deps=False
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
- { src: proc, path: proc, fstype: proc }
|
||||
- { src: sysfs, path: sys, fstype: sysfs }
|
||||
- { src: /dev, path: dev, fstype: none, opts: bind }
|
||||
- { src: devpts, path: dev/pts, fstype: devpts, opts: "gid=5,mode=620" }
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Run OS-specific bootstrap process
|
||||
vars:
|
||||
@@ -39,3 +42,10 @@
|
||||
void: void.yml
|
||||
bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}"
|
||||
ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}"
|
||||
|
||||
- name: Ensure chroot uses live environment DNS
|
||||
ansible.builtin.file:
|
||||
src: /run/NetworkManager/resolv.conf
|
||||
dest: /mnt/etc/resolv.conf
|
||||
state: link
|
||||
force: true
|
||||
|
||||
@@ -6,13 +6,6 @@
|
||||
lookup('vars', bootstrap_var_key) | reject('equalto', '') | join(' ')
|
||||
}}
|
||||
block:
|
||||
- name: Ensure chroot has resolv.conf
|
||||
ansible.builtin.file:
|
||||
src: /run/NetworkManager/resolv.conf
|
||||
dest: /mnt/etc/resolv.conf
|
||||
state: link
|
||||
force: true
|
||||
|
||||
- name: Install openSUSE base packages
|
||||
ansible.builtin.command: >
|
||||
zypper --root /mnt --non-interactive install -t pattern patterns-base-base
|
||||
|
||||
@@ -13,18 +13,6 @@
|
||||
- bootstrap_result.rc != 0
|
||||
- "'grub2-common' not in (bootstrap_result.stderr | default(''))"
|
||||
|
||||
- name: Write resolv.conf into chroot
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/resolv.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
{% for dns in system_cfg.network.dns.servers %}
|
||||
nameserver {{ dns }}
|
||||
{% endfor %}
|
||||
{% if system_cfg.network.dns.search | default([]) | length > 0 %}
|
||||
search {{ system_cfg.network.dns.search | join(' ') }}
|
||||
{% endif %}
|
||||
|
||||
- name: Ensure chroot RHEL DVD directory exists
|
||||
ansible.builtin.file:
|
||||
path: /mnt/usr/local/install/redhat/dvd
|
||||
@@ -37,7 +25,7 @@
|
||||
path: /mnt/usr/local/install/redhat/dvd
|
||||
fstype: none
|
||||
opts: bind
|
||||
state: mounted
|
||||
state: ephemeral
|
||||
|
||||
- name: Rebuild RPM database inside chroot
|
||||
ansible.builtin.command: "{{ chroot_command }} rpm --rebuilddb"
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
register: bootstrap_rocky_base_result
|
||||
changed_when: bootstrap_rocky_base_result.rc == 0
|
||||
|
||||
- name: Ensure chroot has resolv.conf
|
||||
ansible.builtin.file:
|
||||
src: /run/NetworkManager/resolv.conf
|
||||
dest: /mnt/etc/resolv.conf
|
||||
state: link
|
||||
|
||||
- name: Install extra packages
|
||||
ansible.builtin.command: >-
|
||||
{{ chroot_command }} dnf --releasever={{ os_version }} --setopt=install_weak_deps=False
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
---
|
||||
- name: Bootstrap Ubuntu System
|
||||
vars:
|
||||
bootstrap_ubuntu_release: >-
|
||||
{{ 'plucky' if os == 'ubuntu' else 'noble' }}
|
||||
# ubuntu = latest non-LTS, ubuntu-lts = latest LTS
|
||||
bootstrap_ubuntu_release_map:
|
||||
ubuntu: plucky
|
||||
ubuntu-lts: noble
|
||||
bootstrap_ubuntu_release: "{{ bootstrap_ubuntu_release_map[os] | default('noble') }}"
|
||||
bootstrap_ubuntu_package_config: >-
|
||||
{{
|
||||
lookup('vars', bootstrap_var_key)
|
||||
@@ -47,13 +50,6 @@
|
||||
register: bootstrap_ubuntu_base_result
|
||||
changed_when: bootstrap_ubuntu_base_result.rc == 0
|
||||
|
||||
- name: Ensure chroot has resolv.conf
|
||||
ansible.builtin.copy:
|
||||
src: /etc/resolv.conf
|
||||
dest: /mnt/etc/resolv.conf
|
||||
remote_src: true
|
||||
mode: "0644"
|
||||
|
||||
- name: Enable universe repository
|
||||
ansible.builtin.command: "{{ chroot_command }} sed -i '1s|$| universe|' /etc/apt/sources.list"
|
||||
register: bootstrap_ubuntu_repo_result
|
||||
|
||||
@@ -6,13 +6,6 @@
|
||||
lookup('vars', bootstrap_var_key) | reject('equalto', '') | join(' ')
|
||||
}}
|
||||
block:
|
||||
- name: Ensure chroot has resolv.conf
|
||||
ansible.builtin.file:
|
||||
src: /run/NetworkManager/resolv.conf
|
||||
dest: /mnt/etc/resolv.conf
|
||||
state: link
|
||||
force: true
|
||||
|
||||
- name: Install Void Linux base packages
|
||||
ansible.builtin.command: >
|
||||
xbps-install -Sy -r /mnt -R https://repo-default.voidlinux.org/current void-repo-nonfree base-system
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
---
|
||||
# Common conditional packages shared across distributions.
|
||||
# Arch overrides nftables with iptables-nft; SSH package names vary per distro.
|
||||
bootstrap_common_conditional:
|
||||
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
|
||||
- "{{ 'ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}"
|
||||
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
|
||||
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
|
||||
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
|
||||
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
|
||||
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
|
||||
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
|
||||
# Common feature-gated packages. Built as a clean list (no empty strings).
|
||||
# Arch overrides nftables → iptables-nft; SSH package names vary per distro.
|
||||
bootstrap_common_conditional: >-
|
||||
{{
|
||||
(
|
||||
(['firewalld'] if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (['ufw'] if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (['iptables'] if system_cfg.features.firewall.toolkit == 'iptables' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (['nftables'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (['cryptsetup', 'tpm2-tools'] if system_cfg.luks.enabled | bool else [])
|
||||
+ (['qemu-guest-agent'] if hypervisor_type in ['libvirt', 'proxmox'] else [])
|
||||
+ (['open-vm-tools'] if hypervisor_type == 'vmware' else [])
|
||||
)
|
||||
}}
|
||||
|
||||
bootstrap_rhel_base: >-
|
||||
{{
|
||||
@@ -20,13 +23,15 @@ bootstrap_rhel_base: >-
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_rhel_versioned:
|
||||
- grub2
|
||||
- "{{ 'grub2-efi-x64' if os_version_major | default('') == '8' else 'grub2-efi' }}"
|
||||
- "{{ 'grub2-tools-extra' if os_version_major | default('') in ['8', '9'] else '' }}"
|
||||
- "{{ 'python39' if os_version_major | default('') == '8' else 'python' }}"
|
||||
- "{{ 'kernel' if os_version_major | default('') == '10' else '' }}"
|
||||
- "{{ 'zram-generator' if os_version_major | default('') in ['9', '10'] else '' }}"
|
||||
bootstrap_rhel_versioned: >-
|
||||
{{
|
||||
['grub2']
|
||||
+ (['grub2-efi-x64'] if os_version_major | default('') == '8' else ['grub2-efi'])
|
||||
+ (['grub2-tools-extra'] if os_version_major | default('') in ['8', '9'] else [])
|
||||
+ (['python39'] if os_version_major | default('') == '8' else ['python'])
|
||||
+ (['kernel'] if os_version_major | default('') == '10' else [])
|
||||
+ (['zram-generator'] if os_version_major | default('') in ['9', '10'] else [])
|
||||
}}
|
||||
|
||||
bootstrap_rhel: "{{ bootstrap_rhel_base + bootstrap_rhel_versioned }}"
|
||||
|
||||
@@ -34,14 +39,14 @@ bootstrap_almalinux: >-
|
||||
{{
|
||||
bootstrap_rhel_base
|
||||
+ ['grub2', 'grub2-efi', 'dbus-daemon', 'lrzsz',
|
||||
'nfsv4-client-utils', 'nc', 'ppp', 'zram-generator']
|
||||
'nfsv4-client-utils', 'nc', 'ppp', 'python3', 'zram-generator']
|
||||
}}
|
||||
|
||||
bootstrap_rocky: >-
|
||||
{{
|
||||
bootstrap_rhel_base
|
||||
+ ['grub2', 'grub2-efi', 'nfsv4-client-utils', 'nc', 'ppp',
|
||||
'telnet', 'util-linux-core', 'wget', 'zram-generator']
|
||||
'python3', 'telnet', 'util-linux-core', 'wget', 'zram-generator']
|
||||
}}
|
||||
|
||||
bootstrap_fedora: >-
|
||||
@@ -51,28 +56,18 @@ bootstrap_fedora: >-
|
||||
'glibc-langpack-de', 'glibc-langpack-en', 'grub2', 'grub2-efi',
|
||||
'htop', 'iperf3', 'logrotate', 'lrzsz', 'lvm2',
|
||||
'nc', 'nfs-utils', 'nfsv4-client-utils', 'polkit', 'ppp',
|
||||
'ripgrep', 'shim', 'tmux', 'vim-default-editor',
|
||||
'python3', 'ripgrep', 'shim', 'tmux', 'vim-default-editor',
|
||||
'wget', 'zoxide', 'zram-generator', 'zstd']
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_debian_base_common:
|
||||
- btrfs-progs
|
||||
- cron
|
||||
- gnupg
|
||||
- grub-efi
|
||||
- grub-efi-amd64-signed
|
||||
- grub2-common
|
||||
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
|
||||
- "{{ 'cryptsetup-initramfs' if system_cfg.luks.enabled else '' }}"
|
||||
- locales
|
||||
- logrotate
|
||||
- lvm2
|
||||
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
|
||||
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
|
||||
- "{{ 'openssh-server' if system_cfg.features.ssh.enabled | bool else '' }}"
|
||||
- python3
|
||||
- xfsprogs
|
||||
bootstrap_debian_base_common: >-
|
||||
{{
|
||||
['btrfs-progs', 'cron', 'gnupg', 'grub-efi', 'grub-efi-amd64-signed',
|
||||
'grub2-common', 'locales', 'logrotate', 'lvm2', 'python3', 'xfsprogs']
|
||||
+ (['cryptsetup-initramfs'] if system_cfg.luks.enabled | bool else [])
|
||||
+ (['openssh-server'] if system_cfg.features.ssh.enabled | bool else [])
|
||||
}}
|
||||
|
||||
bootstrap_debian_extra_common:
|
||||
- apparmor-utils
|
||||
@@ -101,14 +96,16 @@ bootstrap_debian_extra_common:
|
||||
- wget
|
||||
- zstd
|
||||
|
||||
bootstrap_debian_extra_versioned:
|
||||
- linux-image-amd64
|
||||
- "{{ 'duf' if (os_version | string) not in ['10', '11'] else '' }}"
|
||||
- "{{ 'fastfetch' if (os_version | string) in ['13', 'unstable'] else '' }}"
|
||||
- "{{ 'neofetch' if (os_version | string) == '12' else '' }}"
|
||||
- "{{ 'software-properties-common' if (os_version | string) not in ['13', 'unstable'] else '' }}"
|
||||
- "{{ 'systemd-zram-generator' if (os_version | string) not in ['10', '11'] else '' }}"
|
||||
- "{{ 'tldr' if (os_version | string) not in ['13', 'unstable'] else '' }}"
|
||||
bootstrap_debian_extra_versioned: >-
|
||||
{{
|
||||
['linux-image-amd64']
|
||||
+ (['duf'] if (os_version | string) not in ['10', '11'] else [])
|
||||
+ (['fastfetch'] if (os_version | string) in ['13', 'unstable'] else [])
|
||||
+ (['neofetch'] if (os_version | string) == '12' else [])
|
||||
+ (['software-properties-common'] if (os_version | string) not in ['13', 'unstable'] else [])
|
||||
+ (['systemd-zram-generator'] if (os_version | string) not in ['10', '11'] else [])
|
||||
+ (['tldr'] if (os_version | string) not in ['13', 'unstable'] else [])
|
||||
}}
|
||||
|
||||
bootstrap_debian:
|
||||
base: "{{ bootstrap_debian_base_common }}"
|
||||
@@ -138,30 +135,36 @@ bootstrap_archlinux: >-
|
||||
['base', 'btrfs-progs', 'cronie', 'dhcpcd', 'efibootmgr', 'fastfetch',
|
||||
'fish', 'fzf', 'grub', 'htop', 'libpwquality', 'linux', 'logrotate',
|
||||
'lrzsz', 'lsof', 'lvm2', 'ncdu', 'networkmanager', 'nfs-utils',
|
||||
'ppp', 'prometheus-node-exporter', 'python-psycopg2', 'reflector',
|
||||
'rsync', 'sudo', 'tldr', 'tmux', 'vim', 'wireguard-tools', 'zram-generator']
|
||||
+ [('openssh' if system_cfg.features.ssh.enabled | bool else '')]
|
||||
+ [('iptables-nft' if system_cfg.features.firewall.toolkit == 'nftables' else '')]
|
||||
'ppp', 'python', 'reflector',
|
||||
'rsync', 'sudo', 'tldr', 'tmux', 'vim', 'zram-generator']
|
||||
+ (['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
||||
+ (['iptables-nft'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (bootstrap_common_conditional | reject('equalto', 'nftables') | list)
|
||||
}}
|
||||
|
||||
bootstrap_alpine: >-
|
||||
{{
|
||||
['alpine-base', 'vim']
|
||||
+ [('openssh' if system_cfg.features.ssh.enabled | bool else '')]
|
||||
['alpine-base', 'btrfs-progs', 'chrony', 'curl', 'e2fsprogs',
|
||||
'linux-lts', 'logrotate', 'lvm2', 'python3', 'rsync', 'sudo',
|
||||
'util-linux', 'vim', 'xfsprogs']
|
||||
+ (['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_opensuse: >-
|
||||
{{
|
||||
['vim']
|
||||
+ [('openssh' if system_cfg.features.ssh.enabled | bool else '')]
|
||||
['btrfs-progs', 'chrony', 'curl', 'e2fsprogs',
|
||||
'glibc-locale', 'kernel-default', 'logrotate', 'lvm2', 'NetworkManager',
|
||||
'python3', 'rsync', 'sudo', 'vim', 'xfsprogs']
|
||||
+ (['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_void: >-
|
||||
{{
|
||||
['vim']
|
||||
+ [('openssh' if system_cfg.features.ssh.enabled | bool else '')]
|
||||
['btrfs-progs', 'chrony', 'curl', 'dhcpcd', 'e2fsprogs',
|
||||
'logrotate', 'lvm2', 'python3', 'rsync', 'sudo',
|
||||
'vim', 'xfsprogs']
|
||||
+ (['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
---
|
||||
cis_permission_targets: >-
|
||||
{{
|
||||
[
|
||||
{ "path": "/mnt/etc/ssh/sshd_config", "mode": "0600" },
|
||||
{ "path": "/mnt/etc/cron.hourly", "mode": "0700" },
|
||||
{ "path": "/mnt/etc/cron.daily", "mode": "0700" },
|
||||
{ "path": "/mnt/etc/cron.weekly", "mode": "0700" },
|
||||
{ "path": "/mnt/etc/cron.monthly", "mode": "0700" },
|
||||
{ "path": "/mnt/etc/cron.d", "mode": "0700" },
|
||||
{ "path": "/mnt/etc/crontab", "mode": "0600" },
|
||||
{ "path": "/mnt/etc/logrotate.conf", "mode": "0644" },
|
||||
{ "path": "/mnt/usr/sbin/pppd", "mode": "0754" } if os != "rhel" else None,
|
||||
{
|
||||
"path": "/mnt/usr/bin/"
|
||||
+ ("fusermount3" if os in ["archlinux", "fedora", "rocky"] or os == "rhel" or (os == "debian" and (os_version | string) == "12") else "fusermount"),
|
||||
"mode": "755"
|
||||
},
|
||||
{ "path": "/mnt/usr/bin/" + ("write.ul" if os == "debian" and (os_version | string) == "11" else "write"), "mode": "755" }
|
||||
] | reject("none")
|
||||
}}
|
||||
# Platform-specific binary names for CIS permission targets
|
||||
cis_fusermount_binary: "{{ 'fusermount3' if is_rhel | default(false) | bool else 'fusermount' }}"
|
||||
cis_write_binary: "{{ 'write' if is_rhel | default(false) | bool else 'wall' }}"
|
||||
|
||||
cis_permission_targets:
|
||||
- { path: "/mnt/etc/ssh/sshd_config", mode: "0600" }
|
||||
- { path: "/mnt/etc/cron.hourly", mode: "0700" }
|
||||
- { path: "/mnt/etc/cron.daily", mode: "0700" }
|
||||
- { path: "/mnt/etc/cron.weekly", mode: "0700" }
|
||||
- { path: "/mnt/etc/cron.monthly", mode: "0700" }
|
||||
- { path: "/mnt/etc/cron.d", mode: "0700" }
|
||||
- { path: "/mnt/etc/crontab", mode: "0600" }
|
||||
- { path: "/mnt/etc/logrotate.conf", mode: "0644" }
|
||||
- { path: "/mnt/usr/sbin/pppd", mode: "0754" }
|
||||
- { path: "/mnt/usr/bin/{{ cis_fusermount_binary }}", mode: "0755" }
|
||||
- { path: "/mnt/usr/bin/{{ cis_write_binary }}", mode: "0755" }
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
regexp: "^(\\s*)umask\\s+\\d+"
|
||||
line: "umask 027"
|
||||
|
||||
# Non-RHEL/non-Debian distros: loop evaluates to [] (intentional skip)
|
||||
- name: Prevent Login to Accounts With Empty Password
|
||||
ansible.builtin.replace:
|
||||
dest: "{{ item }}"
|
||||
regexp: "\\s*nullok"
|
||||
replace: ""
|
||||
loop:
|
||||
- /mnt/etc/pam.d/system-auth
|
||||
- /mnt/etc/pam.d/password-auth
|
||||
loop: >-
|
||||
{{
|
||||
['/mnt/etc/pam.d/system-auth', '/mnt/etc/pam.d/password-auth']
|
||||
if is_rhel | bool
|
||||
else (
|
||||
['/mnt/etc/pam.d/common-auth', '/mnt/etc/pam.d/common-password']
|
||||
if is_debian | bool
|
||||
else []
|
||||
)
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
- name: Configure System Cryptography Policy
|
||||
when: os == "rhel" or os in ["almalinux", "rocky"]
|
||||
when: os in (os_family_rhel | difference(['fedora']))
|
||||
ansible.builtin.command: "{{ chroot_command }} /usr/bin/update-crypto-policies --set DEFAULT:NO-SHA1"
|
||||
register: cis_crypto_policy_result
|
||||
changed_when: "'Setting system-wide crypto-policies to' in cis_crypto_policy_result.stdout"
|
||||
@@ -9,4 +9,4 @@
|
||||
ansible.builtin.command: >
|
||||
{{ chroot_command }} systemctl mask {{ 'nftables' if system_cfg.features.firewall.toolkit == 'iptables' else 'iptables' }} bluetooth rpcbind
|
||||
register: cis_mask_services_result
|
||||
changed_when: cis_mask_services_result.rc == 0
|
||||
changed_when: "'Created symlink' in cis_mask_services_result.stderr"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
install hfs /bin/false
|
||||
install hfsplus /bin/false
|
||||
install cramfs /bin/false
|
||||
# Note: disabling squashfs breaks snap (Ubuntu). Remove for snap-dependent hosts.
|
||||
install squashfs /bin/false
|
||||
install udf /bin/false
|
||||
install usb-storage /bin/false
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item.path }}"
|
||||
loop: "{{ cis_permission_targets }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
register: cis_permission_stats
|
||||
changed_when: false
|
||||
|
||||
@@ -13,4 +15,6 @@
|
||||
group: "{{ item.item.group | default(omit) }}"
|
||||
mode: "{{ item.item.mode }}"
|
||||
loop: "{{ cis_permission_stats.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.path }}"
|
||||
when: item.stat.exists
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
- { path: /mnt/etc/security/pwquality.conf, content: ocredit = -1 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, content: lcredit = -1 }
|
||||
- { path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}', content: umask 077 }
|
||||
- { path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}', content: export TMOUT=3000 }
|
||||
- { path: '/mnt/{{ "usr/lib/systemd/journald.conf" if os == "fedora" else "etc/systemd/journald.conf" }}', content: Storage=persistent }
|
||||
- { path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}', content: export TMOUT=900 }
|
||||
- { path: '/mnt/{{ "usr/lib/systemd/journald.conf" if is_rhel | bool else "etc/systemd/journald.conf" }}', content: Storage=persistent }
|
||||
- { path: /mnt/etc/sudoers, content: Defaults logfile="/var/log/sudo.log" }
|
||||
- { path: /mnt/etc/pam.d/su, content: auth required pam_wheel.so }
|
||||
- path: >-
|
||||
@@ -44,3 +44,5 @@
|
||||
password [success=1 default=ignore] pam_unix.so obscure sha512 remember=5
|
||||
- { path: /mnt/etc/hosts.deny, content: "ALL: ALL" }
|
||||
- { path: /mnt/etc/hosts.allow, content: "sshd: ALL" }
|
||||
loop_control:
|
||||
label: "{{ item.content }}"
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
- { option: GSSAPIAuthentication, value: "no" }
|
||||
- { option: AllowAgentForwarding, value: "no" }
|
||||
- { option: AllowTcpForwarding, value: "no" }
|
||||
- { option: ChallengeResponseAuthentication, value: "no" }
|
||||
- { option: KbdInteractiveAuthentication, value: "no" }
|
||||
- { option: GatewayPorts, value: "no" }
|
||||
- { option: X11Forwarding, value: "no" }
|
||||
- { option: PermitUserEnvironment, value: "no" }
|
||||
@@ -29,17 +29,34 @@
|
||||
- { option: ClientAliveCountMax, value: "1" }
|
||||
- { option: PermitTunnel, value: "no" }
|
||||
- { option: Banner, value: /etc/issue.net }
|
||||
loop_control:
|
||||
label: "{{ item.option }}"
|
||||
|
||||
- name: Detect target OpenSSH version
|
||||
ansible.builtin.shell: >-
|
||||
set -o pipefail && {{ chroot_command }} ssh -V 2>&1 | grep -oP 'OpenSSH_\K[0-9]+\.[0-9]+'
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: cis_sshd_openssh_version
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Append CIS specific configurations to sshd_config
|
||||
vars:
|
||||
cis_sshd_has_mlkem: "{{ (cis_sshd_openssh_version.stdout | default('0.0') is version('9.9', '>=')) }}"
|
||||
cis_sshd_kex: >-
|
||||
{{
|
||||
(['mlkem768x25519-sha256'] if cis_sshd_has_mlkem | bool else [])
|
||||
+ ['curve25519-sha256@libssh.org', 'ecdh-sha2-nistp521', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp256']
|
||||
}}
|
||||
ansible.builtin.blockinfile:
|
||||
path: /mnt/etc/ssh/sshd_config
|
||||
marker: "# {mark} CIS SSH HARDENING"
|
||||
block: |-
|
||||
## CIS Specific
|
||||
Protocol 2
|
||||
### Ciphers and keying ###
|
||||
RekeyLimit 512M 6h
|
||||
KexAlgorithms mlkem768x25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
|
||||
KexAlgorithms {{ cis_sshd_kex | join(',') }}
|
||||
Ciphers aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
|
||||
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
|
||||
###########################
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
mode: "0644"
|
||||
content: |
|
||||
## CIS Sysctl configurations
|
||||
fs.suid_dumpable=0
|
||||
kernel.dmesg_restrict=1
|
||||
kernel.yama.ptrace_scope=1
|
||||
kernel.randomize_va_space=2
|
||||
# Network
|
||||
# Disable forwarding; override in inventory for routers/containers
|
||||
net.ipv4.ip_forward=0
|
||||
net.ipv4.tcp_syncookies=1
|
||||
net.ipv4.icmp_echo_ignore_broadcasts=1
|
||||
@@ -24,6 +27,7 @@
|
||||
net.ipv4.conf.default.send_redirects=0
|
||||
net.ipv4.conf.default.accept_redirects=0
|
||||
net.ipv6.conf.all.accept_redirects=0
|
||||
# Disable IPv6; override in inventory if IPv6 is needed
|
||||
net.ipv6.conf.all.disable_ipv6=1
|
||||
net.ipv6.conf.default.accept_redirects=0
|
||||
net.ipv6.conf.default.disable_ipv6=1
|
||||
|
||||
21
roles/cis/vars/main.yml
Normal file
21
roles/cis/vars/main.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
# OS-specific binary names for CIS permission targets.
|
||||
# fusermount3 is the modern name; older distros still use fusermount.
|
||||
cis_fusermount_binary: >-
|
||||
{{
|
||||
'fusermount3'
|
||||
if (
|
||||
os in ['archlinux', 'fedora', 'rocky', 'rhel']
|
||||
or (os == 'debian' and (os_version | string) not in ['10', '11'])
|
||||
or (os == 'almalinux')
|
||||
)
|
||||
else 'fusermount'
|
||||
}}
|
||||
|
||||
# write.ul is the Debian 11 name; all others use write.
|
||||
cis_write_binary: >-
|
||||
{{
|
||||
'write.ul'
|
||||
if (os == 'debian' and (os_version | string) == '11')
|
||||
else 'write'
|
||||
}}
|
||||
@@ -1,4 +1,10 @@
|
||||
---
|
||||
# Post-reboot verification
|
||||
cleanup_verify_boot: true
|
||||
cleanup_boot_timeout: 300
|
||||
cleanup_remove_on_failure: true
|
||||
|
||||
# Libvirt paths
|
||||
cleanup_libvirt_image_dir: >-
|
||||
{{
|
||||
system_cfg.path
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
name: "{{ hostname }}"
|
||||
state: running
|
||||
|
||||
# delegate_to inventory_hostname: overrides play-level localhost to run wait_for_connection against the VM
|
||||
- name: Wait for VM to boot up
|
||||
delegate_to: "{{ inventory_hostname }}"
|
||||
ansible.builtin.wait_for_connection:
|
||||
|
||||
@@ -3,25 +3,32 @@
|
||||
when: hypervisor_type == "proxmox"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Cleanup Setup Disks
|
||||
module_defaults:
|
||||
community.proxmox.proxmox_disk:
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password }}"
|
||||
name: "{{ hostname }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
disk: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- ide0
|
||||
- ide2
|
||||
|
||||
- name: Start the VM
|
||||
community.proxmox.proxmox_kvm:
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password }}"
|
||||
node: "{{ hypervisor_cfg.host }}"
|
||||
block:
|
||||
- name: Cleanup Setup Disks
|
||||
community.proxmox.proxmox_disk:
|
||||
name: "{{ hostname }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
disk: "{{ item }}"
|
||||
state: absent
|
||||
loop: >-
|
||||
{{
|
||||
['ide0', 'ide2']
|
||||
+ (['ide1'] if not (os == 'rhel' and system_cfg.features.rhel_repo.source == 'iso') else [])
|
||||
}}
|
||||
failed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Start the VM
|
||||
community.proxmox.proxmox_kvm:
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
state: restarted
|
||||
|
||||
@@ -6,16 +6,7 @@
|
||||
ansible.builtin.include_tasks: shutdown.yml
|
||||
|
||||
- name: Cleanup hypervisor resources
|
||||
ansible.builtin.include_tasks: proxmox.yml
|
||||
|
||||
- name: Cleanup vCenter resources
|
||||
ansible.builtin.include_tasks: vmware.yml
|
||||
|
||||
- name: Cleanup libvirt resources
|
||||
ansible.builtin.include_tasks: libvirt.yml
|
||||
|
||||
- name: Cleanup Xen resources
|
||||
ansible.builtin.include_tasks: xen.yml
|
||||
ansible.builtin.include_tasks: "{{ hypervisor_type }}.yml"
|
||||
|
||||
- name: Determine post-reboot connectivity
|
||||
ansible.builtin.set_fact:
|
||||
@@ -34,25 +25,27 @@
|
||||
)
|
||||
) | bool
|
||||
}}
|
||||
changed_when: false
|
||||
|
||||
- name: Check VM accessibility after reboot
|
||||
when:
|
||||
- cleanup_verify_boot | bool
|
||||
- system_cfg.type == "virtual"
|
||||
- cleanup_post_reboot_can_connect | bool
|
||||
block:
|
||||
- name: Attempt to connect to VM
|
||||
delegate_to: "{{ inventory_hostname }}"
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: 300
|
||||
timeout: "{{ cleanup_boot_timeout }}"
|
||||
register: cleanup_vm_connection_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: VM failed to boot - initiate cleanup
|
||||
when:
|
||||
- cleanup_remove_on_failure | bool
|
||||
- cleanup_vm_connection_check is defined
|
||||
- cleanup_vm_connection_check.failed | bool
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
block:
|
||||
- name: VM boot failure detected - removing VM
|
||||
ansible.builtin.debug:
|
||||
@@ -61,32 +54,23 @@
|
||||
This VM was created in the current playbook run and will be removed
|
||||
to prevent orphaned resources.
|
||||
|
||||
- name: Remove VM for libvirt
|
||||
when:
|
||||
- hypervisor_type == "libvirt"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
- name: Remove failed libvirt VM
|
||||
when: hypervisor_type == "libvirt"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Destroy libvirt VM
|
||||
community.libvirt.virt:
|
||||
name: "{{ hostname }}"
|
||||
state: destroyed
|
||||
failed_when: false
|
||||
|
||||
- name: Undefine VM for libvirt
|
||||
when:
|
||||
- hypervisor_type == "libvirt"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
- name: Undefine libvirt VM
|
||||
community.libvirt.virt:
|
||||
name: "{{ hostname }}"
|
||||
command: undefine
|
||||
|
||||
- name: Remove VM disk for libvirt
|
||||
when:
|
||||
- hypervisor_type == "libvirt"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
- name: Remove libvirt VM disks
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
@@ -94,83 +78,66 @@
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Remove cloud-init disk for libvirt
|
||||
when:
|
||||
- hypervisor_type == "libvirt"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
- name: Remove libvirt cloud-init disk
|
||||
ansible.builtin.file:
|
||||
path: "{{ virtualization_libvirt_cloudinit_path }}"
|
||||
state: absent
|
||||
|
||||
- name: Remove VM for proxmox
|
||||
when:
|
||||
- hypervisor_type == "proxmox"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
- name: Remove failed Proxmox VM
|
||||
when: hypervisor_type == "proxmox"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
module_defaults:
|
||||
community.proxmox.proxmox_kvm:
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password }}"
|
||||
node: "{{ hypervisor_cfg.host }}"
|
||||
no_log: true
|
||||
block:
|
||||
- name: Stop Proxmox VM
|
||||
community.proxmox.proxmox_kvm:
|
||||
name: "{{ hostname }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
state: stopped
|
||||
|
||||
- name: Delete VM for proxmox
|
||||
when:
|
||||
- hypervisor_type == "proxmox"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
- name: Delete Proxmox VM
|
||||
community.proxmox.proxmox_kvm:
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password }}"
|
||||
node: "{{ hypervisor_cfg.host }}"
|
||||
name: "{{ hostname }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
state: absent
|
||||
unprivileged: false
|
||||
|
||||
- name: Remove VM for VMware
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
- name: Remove failed VMware VM
|
||||
when: hypervisor_type == "vmware"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
module_defaults:
|
||||
community.vmware.vmware_guest:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
no_log: true
|
||||
block:
|
||||
- name: Power off VMware VM
|
||||
community.vmware.vmware_guest:
|
||||
name: "{{ hostname }}"
|
||||
folder: "{{ system_cfg.path | default('/') }}"
|
||||
state: poweredoff
|
||||
|
||||
- name: Delete VM for VMware
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
- name: Delete VMware VM
|
||||
community.vmware.vmware_guest:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
name: "{{ hostname }}"
|
||||
folder: "{{ system_cfg.path | default('/') }}"
|
||||
state: absent
|
||||
|
||||
- name: Destroy Xen VM if running
|
||||
when:
|
||||
- hypervisor_type == "xen"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
- name: Remove failed Xen VM
|
||||
when: hypervisor_type == "xen"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Destroy Xen VM if running
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- xl
|
||||
@@ -180,12 +147,7 @@
|
||||
failed_when: false
|
||||
changed_when: cleanup_xen_destroy.rc == 0
|
||||
|
||||
- name: Remove Xen VM disk
|
||||
when:
|
||||
- hypervisor_type == "xen"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
- name: Remove Xen VM disks
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
@@ -194,11 +156,6 @@
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Remove Xen VM config file
|
||||
when:
|
||||
- hypervisor_type == "xen"
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/xen-{{ hostname }}.cfg"
|
||||
state: absent
|
||||
|
||||
@@ -3,36 +3,55 @@
|
||||
when: hypervisor_type == "vmware"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Remove CD-ROM from VM in vCenter
|
||||
module_defaults:
|
||||
community.vmware.vmware_guest:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
name: "{{ hostname }}"
|
||||
cdrom:
|
||||
- controller_number: 0
|
||||
unit_number: 0
|
||||
controller_type: sata
|
||||
type: iso
|
||||
iso_path: "{{ boot_iso }}"
|
||||
state: absent
|
||||
- controller_number: 0
|
||||
unit_number: 1
|
||||
controller_type: sata
|
||||
type: iso
|
||||
iso_path: "{{ rhel_iso if rhel_iso is defined and rhel_iso | length > 0 else omit }}"
|
||||
state: absent
|
||||
failed_when: false
|
||||
|
||||
- name: Start VM in vCenter
|
||||
vmware.vmware.vm_powerstate:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
no_log: true
|
||||
block:
|
||||
- name: Remove CD-ROM from VM in vCenter
|
||||
community.vmware.vmware_guest:
|
||||
name: "{{ hostname }}"
|
||||
cdrom: >-
|
||||
{{
|
||||
[
|
||||
{
|
||||
'controller_number': 0,
|
||||
'unit_number': 0,
|
||||
'controller_type': 'sata',
|
||||
'type': 'iso',
|
||||
'iso_path': boot_iso,
|
||||
'state': 'absent'
|
||||
}
|
||||
]
|
||||
+ (
|
||||
[
|
||||
{
|
||||
'controller_number': 0,
|
||||
'unit_number': 1,
|
||||
'controller_type': 'sata',
|
||||
'type': 'iso',
|
||||
'iso_path': rhel_iso,
|
||||
'state': 'absent'
|
||||
}
|
||||
]
|
||||
if (rhel_iso is defined and rhel_iso | length > 0
|
||||
and not (os == 'rhel' and system_cfg.features.rhel_repo.source == 'iso'))
|
||||
else []
|
||||
)
|
||||
}}
|
||||
failed_when: false
|
||||
|
||||
- name: Start VM in vCenter
|
||||
vmware.vmware.vm_powerstate:
|
||||
name: "{{ hostname }}"
|
||||
state: powered-on
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
when: hypervisor_type == "xen"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
vars:
|
||||
xen_installer_media_enabled: "{{ xen_installer_media_enabled | default(false) }}"
|
||||
block:
|
||||
- name: Ensure Xen disk definitions exist
|
||||
when: virtualization_xen_disks is not defined
|
||||
|
||||
@@ -23,6 +23,22 @@
|
||||
- /mnt/etc/motd.d/insights-client
|
||||
failed_when: false
|
||||
|
||||
- name: Create login banner
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ item }}"
|
||||
content: |
|
||||
**************************************************************
|
||||
* WARNING: Unauthorized access to this system is prohibited. *
|
||||
* All activities are monitored and logged. *
|
||||
* Disconnect immediately if you are not an authorized user. *
|
||||
**************************************************************
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- /mnt/etc/issue
|
||||
- /mnt/etc/issue.net
|
||||
|
||||
- name: Configure sudo banner
|
||||
when: system_cfg.features.banner.sudo | bool
|
||||
block:
|
||||
|
||||
@@ -21,19 +21,19 @@
|
||||
|
||||
- name: Check existing EFI boot entries
|
||||
ansible.builtin.command: efibootmgr
|
||||
register: _efi_entries
|
||||
register: configuration_efi_entries
|
||||
changed_when: false
|
||||
|
||||
- name: Ensure EFI boot entry exists
|
||||
when: ('* ' + _efi_vendor) not in _efi_entries.stdout
|
||||
when: ('* ' + _efi_vendor) not in configuration_efi_entries.stdout
|
||||
ansible.builtin.command: >-
|
||||
efibootmgr -c
|
||||
-L '{{ _efi_vendor }}'
|
||||
-d '{{ install_drive }}'
|
||||
-p 1
|
||||
-l '\EFI\{{ _efi_vendor }}\{{ _efi_loader }}'
|
||||
register: _efi_entry_result
|
||||
changed_when: _efi_entry_result.rc == 0
|
||||
register: configuration_efi_entry_result
|
||||
changed_when: configuration_efi_entry_result.rc == 0
|
||||
|
||||
- name: Ensure lvm2 for non btrfs filesystems
|
||||
when: os == "archlinux" and system_cfg.filesystem != "btrfs"
|
||||
|
||||
@@ -59,6 +59,14 @@
|
||||
when: configuration_luks_auto_method == 'keyfile'
|
||||
ansible.builtin.include_tasks: encryption/keyfile.yml
|
||||
|
||||
- name: Record final LUKS auto-decrypt method
|
||||
ansible.builtin.set_fact:
|
||||
configuration_luks_final_method: "{{ configuration_luks_auto_method }}"
|
||||
|
||||
- name: Report LUKS auto-decrypt configuration
|
||||
ansible.builtin.debug:
|
||||
msg: "LUKS auto-decrypt method: {{ configuration_luks_final_method }}"
|
||||
|
||||
- name: Build LUKS parameters
|
||||
vars:
|
||||
luks_keyfile_in_use: "{{ configuration_luks_auto_method == 'keyfile' }}"
|
||||
@@ -142,7 +150,7 @@
|
||||
regexp: "^HOOKS="
|
||||
line: >-
|
||||
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole
|
||||
block sd-encrypt lvm2 filesystems fsck)
|
||||
block sd-encrypt{{ ' lvm2' if system_cfg.filesystem != 'btrfs' else '' }} filesystems fsck)
|
||||
|
||||
- name: Read mkinitcpio configuration
|
||||
when: os == 'archlinux'
|
||||
@@ -246,7 +254,7 @@
|
||||
mode: "0644"
|
||||
content: "{{ configuration_kernel_cmdline_new }}\n"
|
||||
|
||||
- name: Find BLS entries
|
||||
- name: Find BLS entries for encryption kernel cmdline
|
||||
when: is_rhel | bool
|
||||
ansible.builtin.find:
|
||||
paths: /mnt/boot/loader/entries
|
||||
|
||||
@@ -104,6 +104,13 @@
|
||||
failed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Warn about keyfile enrollment failure
|
||||
when: (configuration_luks_keyfile_unlock_test_after.rc | default(1)) != 0
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
LUKS keyfile enrollment failed — falling back to manual unlock at boot.
|
||||
The system will prompt for the LUKS passphrase during startup.
|
||||
|
||||
- name: Fallback to manual LUKS unlock if keyfile enrollment failed
|
||||
when: (configuration_luks_keyfile_unlock_test_after.rc | default(1)) != 0
|
||||
ansible.builtin.set_fact:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
- name: Enroll TPM2 for LUKS
|
||||
block:
|
||||
# Tempfile in chroot /tmp — accessible by both chroot and host commands
|
||||
- name: Create temporary passphrase file for TPM2 enrollment
|
||||
ansible.builtin.tempfile:
|
||||
path: /mnt/tmp
|
||||
@@ -78,6 +79,12 @@
|
||||
chroot stderr={{ configuration_luks_tpm2_enroll_chroot.stderr | default('') }},
|
||||
host stderr={{ configuration_luks_tpm2_enroll_host.stderr | default('') }}
|
||||
rescue:
|
||||
- name: Warn about TPM2 enrollment failure
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
TPM2 enrollment failed — falling back to keyfile auto-decrypt.
|
||||
The system will use a keyfile instead of TPM2 for automatic LUKS unlock.
|
||||
|
||||
- name: Fallback to keyfile auto-decrypt
|
||||
ansible.builtin.set_fact:
|
||||
configuration_luks_auto_method: keyfile
|
||||
@@ -87,4 +94,3 @@
|
||||
ansible.builtin.file:
|
||||
path: "{{ configuration_luks_tpm2_passphrase_tempfile.path }}"
|
||||
state: absent
|
||||
changed_when: false
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
set smartindent
|
||||
set mouse=a
|
||||
insertafter: EOF
|
||||
marker: ""
|
||||
marker: "# {mark} CUSTOM VIM CONFIG"
|
||||
failed_when: false
|
||||
|
||||
- name: Add memory tuning parameters
|
||||
@@ -22,7 +22,7 @@
|
||||
vm.dirty_background_ratio=1
|
||||
vm.dirty_ratio=10
|
||||
vm.page-cluster=10
|
||||
marker: ""
|
||||
marker: "# {mark} MEMORY TUNING"
|
||||
mode: "0644"
|
||||
|
||||
- name: Create zram config
|
||||
@@ -45,28 +45,3 @@
|
||||
src: custom.sh.j2
|
||||
dest: /mnt/etc/profile.d/custom.sh
|
||||
mode: "0644"
|
||||
|
||||
- name: Create login banner
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ item }}"
|
||||
content: |
|
||||
**************************************************************
|
||||
* WARNING: Unauthorized access to this system is prohibited. *
|
||||
* All activities are monitored and logged. *
|
||||
* Disconnect immediately if you are not an authorized user. *
|
||||
**************************************************************
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- /mnt/etc/issue
|
||||
- /mnt/etc/issue.net
|
||||
|
||||
- name: Remove motd files
|
||||
when: os == "rhel"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /mnt/etc/motd.d/cockpit
|
||||
- /mnt/etc/motd.d/insights-client
|
||||
|
||||
@@ -23,8 +23,19 @@
|
||||
regexp: "(xfs.*?)(attr2)"
|
||||
replace: "\\1allocsize=64m"
|
||||
|
||||
- name: Remove RHEL ISO fstab entry when not using local repo
|
||||
when:
|
||||
- os == "rhel"
|
||||
- system_cfg.features.rhel_repo.source != "iso"
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/fstab
|
||||
regexp: "^.*\\/dvd.*$"
|
||||
state: absent
|
||||
|
||||
- name: Replace ISO UUID entry with /dev/sr0 in fstab
|
||||
when: os == "rhel"
|
||||
when:
|
||||
- os == "rhel"
|
||||
- system_cfg.features.rhel_repo.source == "iso"
|
||||
vars:
|
||||
configuration_fstab_dvd_line: >-
|
||||
{{
|
||||
@@ -39,7 +50,10 @@
|
||||
state: present
|
||||
|
||||
- name: Write image from RHEL ISO to the target machine
|
||||
when: os == "rhel" and hypervisor_type == 'vmware'
|
||||
when:
|
||||
- os == "rhel"
|
||||
- hypervisor_type == "vmware"
|
||||
- system_cfg.features.rhel_repo.source == "iso"
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- dd
|
||||
@@ -63,3 +77,4 @@
|
||||
- { regexp: "^tmpfs\\s+/dev/shm\\s+", line: "tmpfs /dev/shm tmpfs defaults,nosuid,nodev,noexec 0 0" }
|
||||
loop_control:
|
||||
loop_var: fstab_entry
|
||||
label: "{{ fstab_entry.regexp }}"
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
line: GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3"
|
||||
- regexp: ^GRUB_TIMEOUT=
|
||||
line: GRUB_TIMEOUT=1
|
||||
loop_control:
|
||||
label: "{{ item.line }}"
|
||||
|
||||
- name: Ensure grub defaults file exists for RHEL-based systems
|
||||
when: is_rhel | bool
|
||||
@@ -95,7 +97,7 @@
|
||||
mode: "0644"
|
||||
content: "{{ configuration_kernel_cmdline_base }}\n"
|
||||
|
||||
- name: Find BLS entries
|
||||
- name: Find BLS entries for GRUB configuration
|
||||
ansible.builtin.find:
|
||||
paths: /mnt/boot/loader/entries
|
||||
patterns: "*.conf"
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
line: "{{ item.line }}"
|
||||
loop:
|
||||
- { regex: "{{ system_cfg.locale }} UTF-8", line: "{{ system_cfg.locale }} UTF-8" }
|
||||
loop_control:
|
||||
label: "{{ item.line }}"
|
||||
|
||||
- name: Generate locales
|
||||
when: not is_rhel | bool
|
||||
@@ -45,7 +47,7 @@
|
||||
|
||||
- name: Set hostname
|
||||
ansible.builtin.copy:
|
||||
content: "{{ configuration_hostname_fqdn }}"
|
||||
content: "{{ configuration_hostname_fqdn.split('.')[0] }}"
|
||||
dest: /mnt/etc/hostname
|
||||
mode: "0644"
|
||||
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
---
|
||||
- name: Include configuration tasks
|
||||
ansible.builtin.include_tasks: "{{ configuration_task }}"
|
||||
when: configuration_task.when | default(true)
|
||||
ansible.builtin.include_tasks: "{{ configuration_task.file }}"
|
||||
loop:
|
||||
- banner.yml
|
||||
- fstab.yml
|
||||
- locales.yml
|
||||
- ssh.yml
|
||||
- services.yml
|
||||
- grub.yml
|
||||
- encryption.yml
|
||||
- bootloader.yml
|
||||
- extras.yml
|
||||
- network.yml
|
||||
- users.yml
|
||||
- sudo.yml
|
||||
- selinux.yml
|
||||
- file: banner.yml
|
||||
- file: fstab.yml
|
||||
- file: locales.yml
|
||||
- file: ssh.yml
|
||||
- file: services.yml
|
||||
- file: grub.yml
|
||||
- file: encryption.yml
|
||||
when: "{{ system_cfg.luks.enabled | bool }}"
|
||||
- file: bootloader.yml
|
||||
- file: extras.yml
|
||||
- file: network.yml
|
||||
- file: users.yml
|
||||
- file: sudo.yml
|
||||
- file: selinux.yml
|
||||
when: "{{ is_rhel | bool }}"
|
||||
loop_control:
|
||||
loop_var: configuration_task
|
||||
label: "{{ configuration_task.file }}"
|
||||
|
||||
@@ -29,88 +29,9 @@
|
||||
- configuration_detected_interfaces | length > 0
|
||||
fail_msg: Failed to detect any network interfaces.
|
||||
|
||||
- name: Configure NetworkManager profiles
|
||||
when: os not in ["alpine", "void"]
|
||||
block:
|
||||
- name: Copy NetworkManager keyfile per interface
|
||||
- name: Configure networking
|
||||
vars:
|
||||
configuration_iface: "{{ item }}"
|
||||
configuration_iface_name: "{{ configuration_detected_interfaces[idx] | default('eth' ~ idx) }}"
|
||||
configuration_net_uuid: "{{ ('LAN-' ~ idx ~ '-' ~ hostname) | ansible.builtin.to_uuid }}"
|
||||
ansible.builtin.template:
|
||||
src: network.j2
|
||||
dest: "/mnt/etc/NetworkManager/system-connections/LAN-{{ idx }}.nmconnection"
|
||||
mode: "0600"
|
||||
loop: "{{ system_cfg.network.interfaces }}"
|
||||
loop_control:
|
||||
index_var: idx
|
||||
label: "LAN-{{ idx }}"
|
||||
|
||||
- name: Fix Ubuntu unmanaged devices
|
||||
when: os in ["ubuntu", "ubuntu-lts"]
|
||||
ansible.builtin.file:
|
||||
path: /mnt/etc/NetworkManager/conf.d/10-globally-managed-devices.conf
|
||||
state: touch
|
||||
mode: "0644"
|
||||
|
||||
- name: Configure Alpine networking
|
||||
when: os == "alpine"
|
||||
vars:
|
||||
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
|
||||
block:
|
||||
- name: Write Alpine network interfaces
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/network/interfaces
|
||||
mode: "0644"
|
||||
content: |
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
{% for iface in system_cfg.network.interfaces %}
|
||||
{% set iface_name = configuration_detected_interfaces[loop.index0] | default(iface.name | default('eth' ~ loop.index0)) %}
|
||||
{% set has_static = (iface.ip | default('') | string | length) > 0 %}
|
||||
|
||||
auto {{ iface_name }}
|
||||
iface {{ iface_name }} inet {{ 'static' if has_static else 'dhcp' }}
|
||||
{% if has_static %}
|
||||
address {{ iface.ip }}/{{ iface.prefix }}
|
||||
{% if iface.gateway | default('') | string | length %}
|
||||
gateway {{ iface.gateway }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
- name: Set Alpine DNS resolvers
|
||||
when: configuration_dns_list | length > 0
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/resolv.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
{% for resolver in configuration_dns_list %}
|
||||
nameserver {{ resolver }}
|
||||
{% endfor %}
|
||||
|
||||
- name: Configure Void networking
|
||||
when: os == "void"
|
||||
vars:
|
||||
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
|
||||
block:
|
||||
- name: Write dhcpcd configuration
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/dhcpcd.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
{% for iface in system_cfg.network.interfaces %}
|
||||
{% set iface_name = configuration_detected_interfaces[loop.index0] | default(iface.name | default('eth' ~ loop.index0)) %}
|
||||
{% set has_static = (iface.ip | default('') | string | length) > 0 %}
|
||||
{% if has_static %}
|
||||
interface {{ iface_name }}
|
||||
static ip_address={{ iface.ip }}/{{ iface.prefix }}
|
||||
{% if iface.gateway | default('') | string | length %}
|
||||
static routers={{ iface.gateway }}
|
||||
{% endif %}
|
||||
{% if loop.index0 == 0 and configuration_dns_list | length > 0 %}
|
||||
static domain_name_servers={{ configuration_dns_list | join(' ') }}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
configuration_network_task_map:
|
||||
alpine: network_alpine.yml
|
||||
void: network_void.yml
|
||||
ansible.builtin.include_tasks: "{{ configuration_network_task_map[os] | default('network_nm.yml') }}"
|
||||
|
||||
37
roles/configuration/tasks/network_alpine.yml
Normal file
37
roles/configuration/tasks/network_alpine.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
- name: Write Alpine network interfaces
|
||||
vars:
|
||||
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/network/interfaces
|
||||
mode: "0644"
|
||||
content: |
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
{% for iface in system_cfg.network.interfaces %}
|
||||
{% set inv_name = iface.name | default('') | string %}
|
||||
{% set det_name = configuration_detected_interfaces[loop.index0] | default('eth' ~ loop.index0) %}
|
||||
{% set iface_name = inv_name if inv_name | length > 0 else det_name %}
|
||||
{% set has_static = (iface.ip | default('') | string | length) > 0 %}
|
||||
|
||||
auto {{ iface_name }}
|
||||
iface {{ iface_name }} inet {{ 'static' if has_static else 'dhcp' }}
|
||||
{% if has_static %}
|
||||
address {{ iface.ip }}/{{ iface.prefix }}
|
||||
{% if iface.gateway | default('') | string | length %}
|
||||
gateway {{ iface.gateway }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
- name: Set Alpine DNS resolvers
|
||||
vars:
|
||||
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
|
||||
when: configuration_dns_list | length > 0
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/resolv.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
{% for resolver in configuration_dns_list %}
|
||||
nameserver {{ resolver }}
|
||||
{% endfor %}
|
||||
26
roles/configuration/tasks/network_nm.yml
Normal file
26
roles/configuration/tasks/network_nm.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
- name: Copy NetworkManager keyfile per interface
|
||||
vars:
|
||||
configuration_iface: "{{ item }}"
|
||||
configuration_iface_name: >-
|
||||
{{
|
||||
item.name
|
||||
if (item.name | default('') | string | length) > 0
|
||||
else (configuration_detected_interfaces[idx] | default('eth' ~ idx))
|
||||
}}
|
||||
configuration_net_uuid: "{{ ('LAN-' ~ idx ~ '-' ~ hostname) | ansible.builtin.to_uuid }}"
|
||||
ansible.builtin.template:
|
||||
src: network.j2
|
||||
dest: "/mnt/etc/NetworkManager/system-connections/LAN-{{ idx }}.nmconnection"
|
||||
mode: "0600"
|
||||
loop: "{{ system_cfg.network.interfaces }}"
|
||||
loop_control:
|
||||
index_var: idx
|
||||
label: "LAN-{{ idx }}"
|
||||
|
||||
- name: Fix Ubuntu unmanaged devices
|
||||
when: os in ["ubuntu", "ubuntu-lts"]
|
||||
ansible.builtin.file:
|
||||
path: /mnt/etc/NetworkManager/conf.d/10-globally-managed-devices.conf
|
||||
state: touch
|
||||
mode: "0644"
|
||||
25
roles/configuration/tasks/network_void.yml
Normal file
25
roles/configuration/tasks/network_void.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
- name: Write dhcpcd configuration
|
||||
vars:
|
||||
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/dhcpcd.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
{% for iface in system_cfg.network.interfaces %}
|
||||
{% set inv_name = iface.name | default('') | string %}
|
||||
{% set det_name = configuration_detected_interfaces[loop.index0] | default('eth' ~ loop.index0) %}
|
||||
{% set iface_name = inv_name if inv_name | length > 0 else det_name %}
|
||||
{% set has_static = (iface.ip | default('') | string | length) > 0 %}
|
||||
{% if has_static %}
|
||||
interface {{ iface_name }}
|
||||
static ip_address={{ iface.ip }}/{{ iface.prefix }}
|
||||
{% if iface.gateway | default('') | string | length %}
|
||||
static routers={{ iface.gateway }}
|
||||
{% endif %}
|
||||
{% if loop.index0 == 0 and configuration_dns_list | length > 0 %}
|
||||
static domain_name_servers={{ configuration_dns_list | join(' ') }}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -1,20 +1,19 @@
|
||||
---
|
||||
- name: Enable Systemd Services
|
||||
- name: Enable systemd services
|
||||
when: os not in ['alpine', 'void']
|
||||
ansible.builtin.command: >
|
||||
{{ chroot_command }} systemctl enable NetworkManager
|
||||
{{ ' firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}
|
||||
{{ ' ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}
|
||||
vars:
|
||||
configuration_systemd_services: >-
|
||||
{{
|
||||
(' ssh' if is_debian | bool else ' sshd')
|
||||
if system_cfg.features.ssh.enabled | bool else ''
|
||||
['NetworkManager']
|
||||
+ (['firewalld'] if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (['ufw'] if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ ([('ssh' if is_debian | bool else 'sshd')] if system_cfg.features.ssh.enabled | bool else [])
|
||||
+ (['logrotate', 'systemd-timesyncd'] if os == 'archlinux' else [])
|
||||
}}
|
||||
{{
|
||||
'logrotate systemd-resolved systemd-timesyncd systemd-networkd'
|
||||
if os == 'archlinux' else ''
|
||||
}}
|
||||
register: configuration_enable_services_result
|
||||
changed_when: configuration_enable_services_result.rc == 0
|
||||
ansible.builtin.command: "{{ chroot_command }} systemctl enable {{ item }}"
|
||||
loop: "{{ configuration_systemd_services }}"
|
||||
register: configuration_enable_service_result
|
||||
changed_when: configuration_enable_service_result.rc == 0
|
||||
|
||||
- name: Enable OpenRC services
|
||||
when: os == 'alpine'
|
||||
@@ -37,7 +36,6 @@
|
||||
path: "/mnt/etc/init.d/{{ item }}"
|
||||
loop: "{{ configuration_openrc_services }}"
|
||||
register: configuration_openrc_service_stats
|
||||
changed_when: false
|
||||
|
||||
- name: Enable OpenRC services
|
||||
ansible.builtin.file:
|
||||
@@ -45,6 +43,8 @@
|
||||
dest: "/mnt/etc/runlevels/default/{{ item.item }}"
|
||||
state: link
|
||||
loop: "{{ configuration_openrc_service_stats.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: item.stat.exists
|
||||
|
||||
- name: Enable runit services
|
||||
@@ -68,7 +68,6 @@
|
||||
path: "/mnt/etc/sv/{{ item }}"
|
||||
loop: "{{ configuration_runit_services }}"
|
||||
register: configuration_runit_service_stats
|
||||
changed_when: false
|
||||
|
||||
- name: Enable runit services
|
||||
ansible.builtin.file:
|
||||
@@ -76,4 +75,6 @@
|
||||
dest: "/mnt/var/service/{{ item.item }}"
|
||||
state: link
|
||||
loop: "{{ configuration_runit_service_stats.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: item.stat.exists
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
---
|
||||
- name: Set root password
|
||||
vars:
|
||||
configuration_root_cmd: >-
|
||||
{{ chroot_command }} /usr/sbin/usermod --password
|
||||
'{{ system_cfg.root.password | password_hash('sha512') }}' root --shell /bin/bash
|
||||
ansible.builtin.command: "{{ configuration_root_cmd }}"
|
||||
ansible.builtin.shell: >-
|
||||
set -o pipefail &&
|
||||
echo 'root:{{ system_cfg.root.password | password_hash("sha512") }}' | {{ chroot_command }} chpasswd -e
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: configuration_root_result
|
||||
changed_when: configuration_root_result.rc == 0
|
||||
no_log: true
|
||||
|
||||
- name: Set root shell
|
||||
ansible.builtin.command: >-
|
||||
{{ chroot_command }} /usr/sbin/usermod --shell {{ system_cfg.root.shell | default('/bin/bash') }} root
|
||||
register: configuration_root_shell_result
|
||||
changed_when: configuration_root_shell_result.rc == 0
|
||||
|
||||
- name: Create user accounts
|
||||
vars:
|
||||
configuration_user_group: >-
|
||||
{{ "sudo" if is_debian | bool else "wheel" }}
|
||||
# UID starts at 1000; safe for fresh installs only
|
||||
configuration_useradd_cmd: >-
|
||||
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
|
||||
--uid {{ 1000 + ansible_loop.index0 }}
|
||||
--groups {{ configuration_user_group }} {{ item.name }}
|
||||
--password {{ item.password | password_hash('sha512') }} --shell /bin/bash
|
||||
--password {{ item.password | password_hash('sha512') }} --shell {{ item.shell | default('/bin/bash') }}
|
||||
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
||||
loop: "{{ system_cfg.users }}"
|
||||
loop_control:
|
||||
@@ -24,6 +32,7 @@
|
||||
label: "{{ item.name }}"
|
||||
register: configuration_user_result
|
||||
changed_when: configuration_user_result.rc == 0
|
||||
no_log: true
|
||||
|
||||
- name: Ensure .ssh directory exists
|
||||
when: item['keys'] | default([]) | length > 0
|
||||
|
||||
@@ -15,11 +15,11 @@ method=manual
|
||||
method=auto
|
||||
{% endif %}
|
||||
{% if idx | int == 0 and dns_list %}
|
||||
dns={{ dns_list | join(';') }}
|
||||
dns={{ dns_list | join(';') }};
|
||||
ignore-auto-dns=true
|
||||
{% endif %}
|
||||
{% if idx | int == 0 and search_list %}
|
||||
dns-search={{ search_list | join(';') }}
|
||||
dns-search={{ search_list | join(';') }};
|
||||
{% endif %}
|
||||
|
||||
[ipv6]
|
||||
|
||||
@@ -132,6 +132,8 @@
|
||||
replace: "PermitEmptyPasswords yes"
|
||||
- regexp: "^#?PermitRootLogin.*"
|
||||
replace: "PermitRootLogin yes"
|
||||
loop_control:
|
||||
label: "{{ item.replace }}"
|
||||
|
||||
- name: Reload SSH service to apply changes
|
||||
ansible.builtin.service:
|
||||
@@ -175,6 +177,8 @@
|
||||
- { name: debootstrap, os: [debian, ubuntu, ubuntu-lts] }
|
||||
- { name: debian-archive-keyring, os: [debian] }
|
||||
- { name: ubuntu-keyring, os: [ubuntu, ubuntu-lts] }
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
retries: 4
|
||||
delay: 15
|
||||
|
||||
@@ -187,15 +191,29 @@
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Detect RHEL ISO device
|
||||
ansible.builtin.command: lsblk -rno NAME,TYPE
|
||||
register: environment_lsblk_result
|
||||
changed_when: false
|
||||
|
||||
- name: Select RHEL ISO device
|
||||
vars:
|
||||
_rom_devices: >-
|
||||
{{
|
||||
environment_lsblk_result.stdout_lines
|
||||
| map('split', ' ')
|
||||
| selectattr('1', 'equalto', 'rom')
|
||||
| map('first')
|
||||
| map('regex_replace', '^', '/dev/')
|
||||
| list
|
||||
}}
|
||||
ansible.builtin.set_fact:
|
||||
environment_rhel_iso_device: >-
|
||||
{{
|
||||
'/dev/sr2'
|
||||
if hypervisor_type == 'libvirt'
|
||||
else '/dev/sr1'
|
||||
_rom_devices[-1]
|
||||
if _rom_devices | length > 1
|
||||
else (_rom_devices[0] | default('/dev/sr1'))
|
||||
}}
|
||||
changed_when: false
|
||||
|
||||
- name: Mount RHEL ISO
|
||||
ansible.posix.mount:
|
||||
@@ -205,6 +223,10 @@
|
||||
opts: "ro,loop"
|
||||
state: mounted
|
||||
|
||||
# Security note: RPM Sequoia signature policy is relaxed to allow
|
||||
# bootstrapping RHEL-family distros from the Arch ISO, where the
|
||||
# host rpm/dnf does not trust target distro GPG keys. Package
|
||||
# integrity is verified by the target system's own rpm after reboot.
|
||||
- name: Relax RPM Sequoia signature policy for RHEL bootstrap
|
||||
when: is_rhel | bool
|
||||
ansible.builtin.copy:
|
||||
|
||||
@@ -1,4 +1,27 @@
|
||||
---
|
||||
# OS family lists — single source of truth for platform detection and validation
|
||||
os_family_rhel:
|
||||
- almalinux
|
||||
- fedora
|
||||
- rhel
|
||||
- rocky
|
||||
os_family_debian:
|
||||
- debian
|
||||
- ubuntu
|
||||
- ubuntu-lts
|
||||
os_supported:
|
||||
- almalinux
|
||||
- alpine
|
||||
- archlinux
|
||||
- debian
|
||||
- fedora
|
||||
- opensuse
|
||||
- rhel
|
||||
- rocky
|
||||
- ubuntu
|
||||
- ubuntu-lts
|
||||
- void
|
||||
|
||||
# User input. Normalized into hypervisor_cfg + hypervisor_type.
|
||||
hypervisor:
|
||||
type: "none"
|
||||
@@ -83,6 +106,9 @@ system_defaults:
|
||||
banner:
|
||||
motd: false
|
||||
sudo: true
|
||||
rhel_repo:
|
||||
source: "iso" # iso|satellite|none — how RHEL systems get packages post-install
|
||||
url: "" # Satellite/custom repo URL when source=satellite
|
||||
chroot:
|
||||
tool: "arch-chroot" # arch-chroot|chroot|systemd-nspawn
|
||||
|
||||
|
||||
100
roles/global_defaults/tasks/_normalize_disks.yml
Normal file
100
roles/global_defaults/tasks/_normalize_disks.yml
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
- name: Normalize system disks input
|
||||
vars:
|
||||
system_disks: "{{ system_cfg.disks | default([]) }}"
|
||||
system_disk_letter_map: "abcdefghijklmnopqrstuvwxyz"
|
||||
system_disk_device_prefix: >-
|
||||
{{
|
||||
{'libvirt': '/dev/vd', 'xen': '/dev/xvd', 'proxmox': '/dev/sd', 'vmware': '/dev/sd'}.get(hypervisor_type, '')
|
||||
if system_cfg.type == 'virtual'
|
||||
else ''
|
||||
}}
|
||||
block:
|
||||
- name: Validate system disks structure
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system_disks is sequence
|
||||
- (system_disks | length) <= 26
|
||||
fail_msg: "system.disks must be a list with at most 26 entries."
|
||||
quiet: true
|
||||
|
||||
- name: Validate system disk entries
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item is mapping
|
||||
- item.mount is not defined or item.mount is mapping
|
||||
fail_msg: "Each disk entry must be a dictionary, and disk.mount (if set) must be a dictionary."
|
||||
quiet: true
|
||||
loop: "{{ system_disks }}"
|
||||
loop_control:
|
||||
label: "{{ item | to_json }}"
|
||||
|
||||
- name: Initialize normalized disk list
|
||||
ansible.builtin.set_fact:
|
||||
system_disks_cfg: []
|
||||
|
||||
- name: Build normalized system disk configuration
|
||||
vars:
|
||||
disk_idx: "{{ ansible_loop.index0 }}"
|
||||
disk_letter: "{{ system_disk_letter_map[disk_idx] }}"
|
||||
disk_cfg_base: "{{ system_disk_defaults | combine(item, recursive=True) }}"
|
||||
disk_mount: "{{ system_disk_defaults.mount | combine((disk_cfg_base.mount | default({})), recursive=True) }}"
|
||||
disk_mount_path: "{{ (disk_mount.path | default('') | string) | trim }}"
|
||||
disk_mount_fstype: >-
|
||||
{{
|
||||
disk_mount.fstype
|
||||
if (disk_mount.fstype | default('') | string | length) > 0
|
||||
else ('ext4' if disk_mount_path | length > 0 else '')
|
||||
}}
|
||||
disk_device: >-
|
||||
{{
|
||||
disk_cfg_base.device
|
||||
if (disk_cfg_base.device | string | length) > 0
|
||||
else (
|
||||
(system_disk_device_prefix ~ disk_letter)
|
||||
if system_cfg.type == 'virtual'
|
||||
else ''
|
||||
)
|
||||
}}
|
||||
disk_partition: >-
|
||||
{{
|
||||
disk_device ~ ('p1' if (disk_device | regex_search('\\d$')) else '1')
|
||||
if disk_device | length > 0
|
||||
else ''
|
||||
}}
|
||||
ansible.builtin.set_fact:
|
||||
system_disks_cfg: >-
|
||||
{{
|
||||
system_disks_cfg + [
|
||||
disk_cfg_base
|
||||
| combine(
|
||||
{
|
||||
'device': disk_device,
|
||||
'mount': {
|
||||
'path': disk_mount_path,
|
||||
'fstype': disk_mount_fstype,
|
||||
'label': disk_mount.label | default('') | string,
|
||||
'opts': disk_mount.opts | default('defaults') | string
|
||||
},
|
||||
'partition': disk_partition
|
||||
},
|
||||
recursive=True
|
||||
)
|
||||
]
|
||||
}}
|
||||
loop: "{{ system_disks }}"
|
||||
loop_control:
|
||||
loop_var: item
|
||||
extended: true
|
||||
label: "{{ item | to_json }}"
|
||||
|
||||
- name: Update system configuration with normalized disks
|
||||
ansible.builtin.set_fact:
|
||||
system_cfg: "{{ system_cfg | combine({'disks': system_disks_cfg}, recursive=True) }}"
|
||||
|
||||
- name: Set install_drive from primary disk
|
||||
when:
|
||||
- system_disks_cfg | length > 0
|
||||
- system_disks_cfg[0].device | string | length > 0
|
||||
ansible.builtin.set_fact:
|
||||
install_drive: "{{ system_disks_cfg[0].device }}"
|
||||
143
roles/global_defaults/tasks/_normalize_system.yml
Normal file
143
roles/global_defaults/tasks/_normalize_system.yml
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
- name: Build normalized system configuration
|
||||
vars:
|
||||
system_raw: "{{ system_defaults | combine(system, recursive=True) }}"
|
||||
system_type: "{{ system_raw.type | string | lower }}"
|
||||
system_os_input: "{{ system_raw.os | default('') | string | lower }}"
|
||||
system_name: >-
|
||||
{{
|
||||
system_raw.name | string | trim
|
||||
if (system_raw.name | default('') | string | trim | length) > 0
|
||||
else inventory_hostname
|
||||
}}
|
||||
ansible.builtin.set_fact:
|
||||
system_cfg:
|
||||
type: "{{ system_type }}"
|
||||
os: "{{ system_os_input if system_os_input | length > 0 else ('archlinux' if system_type == 'physical' else '') }}"
|
||||
version: "{{ system_raw.version | default('') | string }}"
|
||||
filesystem: "{{ system_raw.filesystem | default('') | string | lower }}"
|
||||
name: "{{ system_name }}"
|
||||
id: "{{ system_raw.id | default('') | string }}"
|
||||
cpus: "{{ [system_raw.cpus | default(0) | int, 0] | max }}"
|
||||
memory: "{{ [system_raw.memory | default(0) | int, 0] | max }}"
|
||||
balloon: "{{ [system_raw.balloon | default(0) | int, 0] | max }}"
|
||||
network:
|
||||
bridge: "{{ system_raw.network.bridge | default('') | string }}"
|
||||
vlan: "{{ system_raw.network.vlan | default('') | string }}"
|
||||
ip: "{{ system_raw.network.ip | default('') | string }}"
|
||||
prefix: >-
|
||||
{{
|
||||
(system_raw.network.prefix | int | string)
|
||||
if (system_raw.network.prefix | default('') | string | length) > 0
|
||||
else ''
|
||||
}}
|
||||
gateway: "{{ system_raw.network.gateway | default('') | string }}"
|
||||
dns:
|
||||
servers: "{{ system_raw.network.dns.servers | default([]) }}"
|
||||
search: "{{ system_raw.network.dns.search | default([]) }}"
|
||||
interfaces: >-
|
||||
{{
|
||||
system_raw.network.interfaces
|
||||
if (system_raw.network.interfaces | default([]) | length > 0)
|
||||
else (
|
||||
[{
|
||||
'name': '',
|
||||
'bridge': system_raw.network.bridge | default('') | string,
|
||||
'vlan': system_raw.network.vlan | default('') | string,
|
||||
'ip': system_raw.network.ip | default('') | string,
|
||||
'prefix': (
|
||||
(system_raw.network.prefix | int | string)
|
||||
if (system_raw.network.prefix | default('') | string | length) > 0
|
||||
else ''
|
||||
),
|
||||
'gateway': system_raw.network.gateway | default('') | string
|
||||
}]
|
||||
if (system_raw.network.bridge | default('') | string | length > 0)
|
||||
else []
|
||||
)
|
||||
}}
|
||||
timezone: "{{ system_raw.timezone | default('Europe/Vienna') | string }}"
|
||||
locale: "{{ system_raw.locale | default('en_US.UTF-8') | string }}"
|
||||
keymap: "{{ system_raw.keymap | default('us') | string }}"
|
||||
path: "{{ system_raw.path | default('') | string }}"
|
||||
packages: >-
|
||||
{{
|
||||
(
|
||||
system_raw.packages
|
||||
if system_raw.packages is iterable and system_raw.packages is not string
|
||||
else (system_raw.packages | string).split(',')
|
||||
)
|
||||
| map('trim')
|
||||
| reject('equalto', '')
|
||||
| list
|
||||
}}
|
||||
disks: "{{ system_raw.disks | default([]) }}"
|
||||
users: "{{ system_raw.users | default([]) }}"
|
||||
root:
|
||||
password: "{{ system_raw.root.password | string }}"
|
||||
luks:
|
||||
enabled: "{{ system_raw.luks.enabled | bool }}"
|
||||
passphrase: "{{ system_raw.luks.passphrase | string }}"
|
||||
mapper: "{{ system_raw.luks.mapper | string }}"
|
||||
auto: "{{ system_raw.luks.auto | bool }}"
|
||||
method: "{{ system_raw.luks.method | string | lower }}"
|
||||
tpm2:
|
||||
device: "{{ system_raw.luks.tpm2.device | string }}"
|
||||
pcrs: "{{ system_raw.luks.tpm2.pcrs | string }}"
|
||||
keysize: "{{ system_raw.luks.keysize | int }}"
|
||||
options: "{{ system_raw.luks.options | string }}"
|
||||
type: "{{ system_raw.luks.type | string }}"
|
||||
cipher: "{{ system_raw.luks.cipher | string }}"
|
||||
hash: "{{ system_raw.luks.hash | string }}"
|
||||
iter: "{{ system_raw.luks.iter | int }}"
|
||||
bits: "{{ system_raw.luks.bits | int }}"
|
||||
pbkdf: "{{ system_raw.luks.pbkdf | string }}"
|
||||
urandom: "{{ system_raw.luks.urandom | bool }}"
|
||||
verify: "{{ system_raw.luks.verify | bool }}"
|
||||
features:
|
||||
cis:
|
||||
enabled: "{{ system_raw.features.cis.enabled | bool }}"
|
||||
selinux:
|
||||
enabled: "{{ system_raw.features.selinux.enabled | bool }}"
|
||||
firewall:
|
||||
enabled: "{{ system_raw.features.firewall.enabled | bool }}"
|
||||
backend: "{{ system_raw.features.firewall.backend | string | lower }}"
|
||||
toolkit: "{{ system_raw.features.firewall.toolkit | string | lower }}"
|
||||
ssh:
|
||||
enabled: "{{ system_raw.features.ssh.enabled | bool }}"
|
||||
zstd:
|
||||
enabled: "{{ system_raw.features.zstd.enabled | bool }}"
|
||||
swap:
|
||||
enabled: "{{ system_raw.features.swap.enabled | bool }}"
|
||||
banner:
|
||||
motd: "{{ system_raw.features.banner.motd | bool }}"
|
||||
sudo: "{{ system_raw.features.banner.sudo | bool }}"
|
||||
rhel_repo:
|
||||
source: "{{ system_raw.features.rhel_repo.source | default('iso') | string | lower }}"
|
||||
url: "{{ system_raw.features.rhel_repo.url | default('') | string }}"
|
||||
chroot:
|
||||
tool: "{{ system_raw.features.chroot.tool | string }}"
|
||||
hostname: "{{ system_name }}"
|
||||
os: "{{ system_os_input if system_os_input | length > 0 else ('archlinux' if system_type == 'physical' else '') }}"
|
||||
os_version: "{{ system_raw.version | default('') | string }}"
|
||||
no_log: true
|
||||
|
||||
- name: Populate primary network fields from first interface
|
||||
when:
|
||||
- system_cfg.network.interfaces | length > 0
|
||||
- system_cfg.network.bridge | default('') | string | length == 0
|
||||
vars:
|
||||
_primary: "{{ system_cfg.network.interfaces[0] }}"
|
||||
ansible.builtin.set_fact:
|
||||
system_cfg: >-
|
||||
{{
|
||||
system_cfg | combine({
|
||||
'network': system_cfg.network | combine({
|
||||
'bridge': _primary.bridge | default(''),
|
||||
'vlan': _primary.vlan | default(''),
|
||||
'ip': _primary.ip | default(''),
|
||||
'prefix': _primary.prefix | default(''),
|
||||
'gateway': _primary.gateway | default('')
|
||||
})
|
||||
}, recursive=True)
|
||||
}}
|
||||
57
roles/global_defaults/tasks/_validate_input.yml
Normal file
57
roles/global_defaults/tasks/_validate_input.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
- name: Ensure system input is a dictionary
|
||||
ansible.builtin.set_fact:
|
||||
system: "{{ system | default({}) }}"
|
||||
|
||||
- name: Validate system input types
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system is mapping
|
||||
- system.network is not defined or system.network is mapping
|
||||
- system.users is not defined or (system.users is iterable and system.users is not string and system.users is not mapping)
|
||||
- system.root is not defined or system.root is mapping
|
||||
- system.luks is not defined or system.luks is mapping
|
||||
- system.features is not defined or system.features is mapping
|
||||
fail_msg: "system and its nested keys (network, root, luks, features) must be dictionaries; system.users must be a list."
|
||||
quiet: true
|
||||
|
||||
- name: Validate DNS lists (not strings)
|
||||
when: system.network is defined and system.network.dns is defined
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system.network.dns.servers is not defined or (system.network.dns.servers is iterable and system.network.dns.servers is not string)
|
||||
- system.network.dns.search is not defined or (system.network.dns.search is iterable and system.network.dns.search is not string)
|
||||
fail_msg: "system.network.dns.servers and system.network.dns.search must be lists, not strings."
|
||||
quiet: true
|
||||
|
||||
- name: Validate system.users entries
|
||||
when: system.users is defined and system.users | length > 0
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item is mapping
|
||||
- item.name is defined and (item.name | string | length) > 0
|
||||
- item['keys'] is not defined or (item['keys'] is iterable and item['keys'] is not string)
|
||||
fail_msg: "Each system.users[] entry must be a dict with 'name'; 'keys' must be a list."
|
||||
quiet: true
|
||||
loop: "{{ system.users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name | default('(unnamed)') }}"
|
||||
|
||||
- name: Validate system features input types
|
||||
when: system.features is defined
|
||||
loop: "{{ system_defaults.features | dict2items | map(attribute='key') | list }}"
|
||||
loop_control:
|
||||
label: "system.features.{{ item }}"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (system.features[item] | default({})) is mapping
|
||||
fail_msg: "system.features.{{ item }} must be a dictionary."
|
||||
quiet: true
|
||||
|
||||
- name: Validate system LUKS TPM2 input type
|
||||
when: system.luks is defined and system.luks is mapping
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system.luks.tpm2 is not defined or system.luks.tpm2 is mapping
|
||||
fail_msg: "system.luks.tpm2 must be a dictionary."
|
||||
quiet: true
|
||||
@@ -18,3 +18,4 @@
|
||||
ansible.builtin.set_fact:
|
||||
hypervisor_cfg: "{{ merged }}"
|
||||
hypervisor_type: "{{ merged.type | string | lower }}"
|
||||
no_log: true
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
- name: Set OS family flags
|
||||
ansible.builtin.set_fact:
|
||||
is_rhel: "{{ os in ['almalinux', 'fedora', 'rhel', 'rocky'] }}"
|
||||
is_debian: "{{ os in ['debian', 'ubuntu', 'ubuntu-lts'] }}"
|
||||
is_rhel: "{{ os in os_family_rhel }}"
|
||||
is_debian: "{{ os in os_family_debian }}"
|
||||
|
||||
- name: Normalize OS version for keying
|
||||
when:
|
||||
@@ -49,6 +49,7 @@
|
||||
ansible_password: "{{ system_cfg.users[0].password }}"
|
||||
ansible_become_password: "{{ system_cfg.users[0].password }}"
|
||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||
no_log: true
|
||||
|
||||
- name: Set connection for VMware
|
||||
when: hypervisor_type == "vmware"
|
||||
|
||||
@@ -1,297 +1,9 @@
|
||||
---
|
||||
- name: Ensure system input is a dictionary
|
||||
ansible.builtin.set_fact:
|
||||
system: "{{ system | default({}) }}"
|
||||
- name: Validate raw system input types
|
||||
ansible.builtin.include_tasks: _validate_input.yml
|
||||
|
||||
- name: Validate system input types
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system is mapping
|
||||
- system.network is not defined or system.network is mapping
|
||||
- system.users is not defined or (system.users is iterable and system.users is not string and system.users is not mapping)
|
||||
- system.root is not defined or system.root is mapping
|
||||
- system.luks is not defined or system.luks is mapping
|
||||
- system.features is not defined or system.features is mapping
|
||||
fail_msg: "system and its nested keys (network, root, luks, features) must be dictionaries; system.users must be a list."
|
||||
quiet: true
|
||||
|
||||
- name: Validate DNS lists (not strings)
|
||||
when: system.network is defined and system.network.dns is defined
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system.network.dns.servers is not defined or (system.network.dns.servers is iterable and system.network.dns.servers is not string)
|
||||
- system.network.dns.search is not defined or (system.network.dns.search is iterable and system.network.dns.search is not string)
|
||||
fail_msg: "system.network.dns.servers and system.network.dns.search must be lists, not strings."
|
||||
quiet: true
|
||||
|
||||
- name: Validate system.users entries
|
||||
when: system.users is defined and system.users | length > 0
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item is mapping
|
||||
- item.name is defined and (item.name | string | length) > 0
|
||||
- item['keys'] is not defined or (item['keys'] is iterable and item['keys'] is not string)
|
||||
fail_msg: "Each system.users[] entry must be a dict with 'name'; 'keys' must be a list."
|
||||
quiet: true
|
||||
loop: "{{ system.users }}"
|
||||
loop_control:
|
||||
label: "{{ item.name | default('(unnamed)') }}"
|
||||
|
||||
- name: Validate system features input types
|
||||
when: system.features is defined
|
||||
loop: "{{ system_defaults.features | dict2items | map(attribute='key') | list }}"
|
||||
loop_control:
|
||||
label: "system.features.{{ item }}"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (system.features[item] | default({})) is mapping
|
||||
fail_msg: "system.features.{{ item }} must be a dictionary."
|
||||
quiet: true
|
||||
|
||||
- name: Validate system LUKS TPM2 input type
|
||||
when: system.luks is defined and system.luks is mapping
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system.luks.tpm2 is not defined or system.luks.tpm2 is mapping
|
||||
fail_msg: "system.luks.tpm2 must be a dictionary."
|
||||
quiet: true
|
||||
|
||||
- name: Build normalized system configuration
|
||||
vars:
|
||||
system_raw: "{{ system_defaults | combine(system, recursive=True) }}"
|
||||
system_type: "{{ system_raw.type | string | lower }}"
|
||||
system_os_input: "{{ system_raw.os | default('') | string | lower }}"
|
||||
system_name: >-
|
||||
{{
|
||||
system_raw.name | string | trim
|
||||
if (system_raw.name | default('') | string | trim | length) > 0
|
||||
else inventory_hostname
|
||||
}}
|
||||
ansible.builtin.set_fact:
|
||||
system_cfg:
|
||||
type: "{{ system_type }}"
|
||||
os: "{{ system_os_input if system_os_input | length > 0 else ('archlinux' if system_type == 'physical' else '') }}"
|
||||
version: "{{ system_raw.version | default('') | string }}"
|
||||
filesystem: "{{ system_raw.filesystem | default('') | string | lower }}"
|
||||
name: "{{ system_name }}"
|
||||
id: "{{ system_raw.id | default('') | string }}"
|
||||
cpus: "{{ [system_raw.cpus | default(0) | int, 0] | max }}"
|
||||
memory: "{{ [system_raw.memory | default(0) | int, 0] | max }}"
|
||||
balloon: "{{ [system_raw.balloon | default(0) | int, 0] | max }}"
|
||||
network:
|
||||
bridge: "{{ system_raw.network.bridge | default('') | string }}"
|
||||
vlan: "{{ system_raw.network.vlan | default('') | string }}"
|
||||
ip: "{{ system_raw.network.ip | default('') | string }}"
|
||||
prefix: >-
|
||||
{{
|
||||
(system_raw.network.prefix | int)
|
||||
if (system_raw.network.prefix | default('') | string | length) > 0
|
||||
else ''
|
||||
}}
|
||||
gateway: "{{ system_raw.network.gateway | default('') | string }}"
|
||||
dns:
|
||||
servers: "{{ system_raw.network.dns.servers | default([]) }}"
|
||||
search: "{{ system_raw.network.dns.search | default([]) }}"
|
||||
interfaces: >-
|
||||
{{
|
||||
system_raw.network.interfaces
|
||||
if (system_raw.network.interfaces | default([]) | length > 0)
|
||||
else (
|
||||
[{
|
||||
'name': 'eth0',
|
||||
'bridge': system_raw.network.bridge | default('') | string,
|
||||
'vlan': system_raw.network.vlan | default('') | string,
|
||||
'ip': system_raw.network.ip | default('') | string,
|
||||
'prefix': (
|
||||
(system_raw.network.prefix | int | string)
|
||||
if (system_raw.network.prefix | default('') | string | length) > 0
|
||||
else ''
|
||||
),
|
||||
'gateway': system_raw.network.gateway | default('') | string
|
||||
}]
|
||||
if (system_raw.network.bridge | default('') | string | length > 0)
|
||||
else []
|
||||
)
|
||||
}}
|
||||
timezone: "{{ system_raw.timezone | default('Europe/Vienna') | string }}"
|
||||
locale: "{{ system_raw.locale | default('en_US.UTF-8') | string }}"
|
||||
keymap: "{{ system_raw.keymap | default('us') | string }}"
|
||||
path: "{{ system_raw.path | default('') | string }}"
|
||||
packages: >-
|
||||
{{
|
||||
(
|
||||
system_raw.packages
|
||||
if system_raw.packages is iterable and system_raw.packages is not string
|
||||
else (system_raw.packages | string).split(',')
|
||||
)
|
||||
| map('trim')
|
||||
| reject('equalto', '')
|
||||
| list
|
||||
}}
|
||||
disks: "{{ system_raw.disks | default([]) }}"
|
||||
users: "{{ system_raw.users | default([]) }}"
|
||||
root:
|
||||
password: "{{ system_raw.root.password | string }}"
|
||||
luks:
|
||||
enabled: "{{ system_raw.luks.enabled | bool }}"
|
||||
passphrase: "{{ system_raw.luks.passphrase | string }}"
|
||||
mapper: "{{ system_raw.luks.mapper | string }}"
|
||||
auto: "{{ system_raw.luks.auto | bool }}"
|
||||
method: "{{ system_raw.luks.method | string | lower }}"
|
||||
tpm2:
|
||||
device: "{{ system_raw.luks.tpm2.device | string }}"
|
||||
pcrs: "{{ system_raw.luks.tpm2.pcrs | string }}"
|
||||
keysize: "{{ system_raw.luks.keysize | int }}"
|
||||
options: "{{ system_raw.luks.options | string }}"
|
||||
type: "{{ system_raw.luks.type | string }}"
|
||||
cipher: "{{ system_raw.luks.cipher | string }}"
|
||||
hash: "{{ system_raw.luks.hash | string }}"
|
||||
iter: "{{ system_raw.luks.iter | int }}"
|
||||
bits: "{{ system_raw.luks.bits | int }}"
|
||||
pbkdf: "{{ system_raw.luks.pbkdf | string }}"
|
||||
urandom: "{{ system_raw.luks.urandom | bool }}"
|
||||
verify: "{{ system_raw.luks.verify | bool }}"
|
||||
features:
|
||||
cis:
|
||||
enabled: "{{ system_raw.features.cis.enabled | bool }}"
|
||||
selinux:
|
||||
enabled: "{{ system_raw.features.selinux.enabled | bool }}"
|
||||
firewall:
|
||||
enabled: "{{ system_raw.features.firewall.enabled | bool }}"
|
||||
backend: "{{ system_raw.features.firewall.backend | string | lower }}"
|
||||
toolkit: "{{ system_raw.features.firewall.toolkit | string | lower }}"
|
||||
ssh:
|
||||
enabled: "{{ system_raw.features.ssh.enabled | bool }}"
|
||||
zstd:
|
||||
enabled: "{{ system_raw.features.zstd.enabled | bool }}"
|
||||
swap:
|
||||
enabled: "{{ system_raw.features.swap.enabled | bool }}"
|
||||
banner:
|
||||
motd: "{{ system_raw.features.banner.motd | bool }}"
|
||||
sudo: "{{ system_raw.features.banner.sudo | bool }}"
|
||||
chroot:
|
||||
tool: "{{ system_raw.features.chroot.tool | string }}"
|
||||
hostname: "{{ system_name }}"
|
||||
os: "{{ system_os_input if system_os_input | length > 0 else ('archlinux' if system_type == 'physical' else '') }}"
|
||||
os_version: "{{ system_raw.version | default('') | string }}"
|
||||
|
||||
- name: Populate primary network fields from first interface
|
||||
when:
|
||||
- system_cfg.network.interfaces | length > 0
|
||||
- system_cfg.network.bridge | default('') | string | length == 0
|
||||
vars:
|
||||
_primary: "{{ system_cfg.network.interfaces[0] }}"
|
||||
ansible.builtin.set_fact:
|
||||
system_cfg: >-
|
||||
{{
|
||||
system_cfg | combine({
|
||||
'network': system_cfg.network | combine({
|
||||
'bridge': _primary.bridge | default(''),
|
||||
'vlan': _primary.vlan | default(''),
|
||||
'ip': _primary.ip | default(''),
|
||||
'prefix': _primary.prefix | default(''),
|
||||
'gateway': _primary.gateway | default('')
|
||||
})
|
||||
}, recursive=True)
|
||||
}}
|
||||
|
||||
- name: Normalize system disks input
|
||||
vars:
|
||||
system_disks: "{{ system_cfg.disks | default([]) }}"
|
||||
system_disk_letter_map: "abcdefghijklmnopqrstuvwxyz"
|
||||
system_disk_device_prefix: >-
|
||||
{{
|
||||
{'libvirt': '/dev/vd', 'xen': '/dev/xvd', 'proxmox': '/dev/sd', 'vmware': '/dev/sd'}.get(hypervisor_type, '')
|
||||
if system_cfg.type == 'virtual'
|
||||
else ''
|
||||
}}
|
||||
block:
|
||||
- name: Validate system disks structure
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system_disks is sequence
|
||||
- (system_disks | length) <= 26
|
||||
fail_msg: "system.disks must be a list with at most 26 entries."
|
||||
quiet: true
|
||||
|
||||
- name: Validate system disk entries
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item is mapping
|
||||
- item.mount is not defined or item.mount is mapping
|
||||
fail_msg: "Each disk entry must be a dictionary, and disk.mount (if set) must be a dictionary."
|
||||
quiet: true
|
||||
loop: "{{ system_disks }}"
|
||||
loop_control:
|
||||
label: "{{ item | to_json }}"
|
||||
|
||||
- name: Initialize normalized disk list
|
||||
ansible.builtin.set_fact:
|
||||
system_disks_cfg: []
|
||||
|
||||
- name: Build normalized system disk configuration
|
||||
vars:
|
||||
disk_idx: "{{ ansible_loop.index0 }}"
|
||||
disk_letter: "{{ system_disk_letter_map[disk_idx] }}"
|
||||
disk_cfg_base: "{{ system_disk_defaults | combine(item, recursive=True) }}"
|
||||
disk_mount: "{{ system_disk_defaults.mount | combine((disk_cfg_base.mount | default({})), recursive=True) }}"
|
||||
disk_mount_path: "{{ (disk_mount.path | default('') | string) | trim }}"
|
||||
disk_mount_fstype: >-
|
||||
{{
|
||||
disk_mount.fstype
|
||||
if (disk_mount.fstype | default('') | string | length) > 0
|
||||
else ('ext4' if disk_mount_path | length > 0 else '')
|
||||
}}
|
||||
disk_device: >-
|
||||
{{
|
||||
disk_cfg_base.device
|
||||
if (disk_cfg_base.device | string | length) > 0
|
||||
else (
|
||||
(system_disk_device_prefix ~ disk_letter)
|
||||
if system_cfg.type == 'virtual'
|
||||
else ''
|
||||
)
|
||||
}}
|
||||
disk_partition: >-
|
||||
{{
|
||||
disk_device ~ ('p1' if (disk_device | regex_search('\\d$')) else '1')
|
||||
if disk_device | length > 0
|
||||
else ''
|
||||
}}
|
||||
ansible.builtin.set_fact:
|
||||
system_disks_cfg: >-
|
||||
{{
|
||||
system_disks_cfg + [
|
||||
disk_cfg_base
|
||||
| combine(
|
||||
{
|
||||
'device': disk_device,
|
||||
'mount': {
|
||||
'path': disk_mount_path,
|
||||
'fstype': disk_mount_fstype,
|
||||
'label': disk_mount.label | default('') | string,
|
||||
'opts': disk_mount.opts | default('defaults') | string
|
||||
},
|
||||
'partition': disk_partition
|
||||
},
|
||||
recursive=True
|
||||
)
|
||||
]
|
||||
}}
|
||||
loop: "{{ system_disks }}"
|
||||
loop_control:
|
||||
loop_var: item
|
||||
extended: true
|
||||
label: "{{ item | to_json }}"
|
||||
|
||||
- name: Update system configuration with normalized disks
|
||||
ansible.builtin.set_fact:
|
||||
system_cfg: "{{ system_cfg | combine({'disks': system_disks_cfg}, recursive=True) }}"
|
||||
|
||||
- name: Set install_drive from primary disk
|
||||
when:
|
||||
- system_disks_cfg | length > 0
|
||||
- system_disks_cfg[0].device | string | length > 0
|
||||
ansible.builtin.set_fact:
|
||||
install_drive: "{{ system_disks_cfg[0].device }}"
|
||||
- name: Normalize system configuration
|
||||
ansible.builtin.include_tasks: _normalize_system.yml
|
||||
|
||||
- name: Normalize disk configuration
|
||||
ansible.builtin.include_tasks: _normalize_disks.yml
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- os is defined
|
||||
- os in ["almalinux", "alpine", "archlinux", "debian", "fedora", "opensuse", "rhel", "rocky", "ubuntu", "ubuntu-lts", "void"]
|
||||
- os in os_supported
|
||||
- >-
|
||||
os not in ["debian", "fedora", "rocky", "almalinux", "rhel"]
|
||||
or (os_version is defined and (os_version | string | length) > 0)
|
||||
@@ -123,7 +123,7 @@
|
||||
or (
|
||||
os == "debian" and (os_version | string) in ["10", "11", "12", "13", "unstable"]
|
||||
) or (
|
||||
os == "fedora" and (os_version | string) in ["40", "41", "42", "43"]
|
||||
os == "fedora" and (os_version | int) >= 38 and (os_version | int) <= 45
|
||||
) or (
|
||||
os in ["rocky", "almalinux"]
|
||||
and (os_version | string) is match("^(8|9|10)(\\.\\d+)?$")
|
||||
@@ -131,7 +131,9 @@
|
||||
os == "rhel"
|
||||
and (os_version | string) is match("^(8|9|10)(\\.\\d+)?$")
|
||||
) or (
|
||||
os in ["alpine", "archlinux", "opensuse", "ubuntu", "ubuntu-lts", "void"]
|
||||
os in ["ubuntu", "ubuntu-lts"]
|
||||
) or (
|
||||
os in ["alpine", "archlinux", "opensuse", "void"]
|
||||
)
|
||||
fail_msg: "Invalid os/version specified. Please check README.md for supported values."
|
||||
quiet: true
|
||||
@@ -162,6 +164,7 @@
|
||||
Missing required Proxmox inputs. Define hypervisor.(url,username,password,host,storage),
|
||||
system.id, and system.network.bridge (or system.network.interfaces[]).
|
||||
quiet: true
|
||||
no_log: true
|
||||
|
||||
- name: Validate VMware hypervisor inputs
|
||||
when:
|
||||
@@ -182,6 +185,7 @@
|
||||
Missing required VMware inputs. Define hypervisor.(url,username,password,datacenter,cluster,storage)
|
||||
and system.network.bridge (or system.network.interfaces[]).
|
||||
quiet: true
|
||||
no_log: true
|
||||
|
||||
- name: Validate Xen hypervisor inputs
|
||||
when:
|
||||
@@ -195,6 +199,18 @@
|
||||
fail_msg: "Missing required Xen inputs. Define system.network.bridge (or system.network.interfaces[])."
|
||||
quiet: true
|
||||
|
||||
- name: Validate libvirt hypervisor inputs
|
||||
when:
|
||||
- system_cfg.type == "virtual"
|
||||
- hypervisor_type == "libvirt"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
(system_cfg.network.bridge | default('') | string | length > 0)
|
||||
or (system_cfg.network.interfaces | default([]) | length > 0)
|
||||
fail_msg: "Missing required libvirt inputs. Define system.network.bridge (or system.network.interfaces[])."
|
||||
quiet: true
|
||||
|
||||
- name: Validate virtual installer ISO requirement
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
@@ -245,6 +261,28 @@
|
||||
fail_msg: "Invalid system sizing. Check system.cpus, system.memory, and system.disks[0].size."
|
||||
quiet: true
|
||||
|
||||
- name: Validate at least one user is defined
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system_cfg.users | default([]) | length > 0
|
||||
- system_cfg.users[0].name is defined and (system_cfg.users[0].name | string | length) > 0
|
||||
- system_cfg.users[0].password is defined and (system_cfg.users[0].password | string | length) > 0
|
||||
fail_msg: "At least one user with a name and password must be defined in system.users[]."
|
||||
quiet: true
|
||||
no_log: true
|
||||
|
||||
- name: Validate DNS servers is a list
|
||||
when:
|
||||
- system_cfg.network.dns.servers is defined
|
||||
- system_cfg.network.dns.servers | length > 0
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system_cfg.network.dns.servers is iterable
|
||||
- system_cfg.network.dns.servers is not string
|
||||
- system_cfg.network.dns.servers is not mapping
|
||||
fail_msg: "system.network.dns.servers must be a list."
|
||||
quiet: true
|
||||
|
||||
- name: Validate all virtual disks have a positive size
|
||||
when: system_cfg.type == "virtual"
|
||||
ansible.builtin.assert:
|
||||
|
||||
@@ -6,20 +6,40 @@ partitioning_efi_size_mib: 512
|
||||
partitioning_efi_start_mib: 1
|
||||
partitioning_efi_end_mib: "{{ (partitioning_efi_start_mib | int) + (partitioning_efi_size_mib | int) }}"
|
||||
partitioning_boot_size_mib: 1024
|
||||
partitioning_vg_name: sys
|
||||
partitioning_use_full_disk: true
|
||||
|
||||
# LVM logical volume sizing
|
||||
partitioning_lvm_var_gb: 2
|
||||
partitioning_lvm_var_log_gb: 2
|
||||
partitioning_lvm_var_log_audit_gb: 1.5
|
||||
|
||||
# Disk overhead subtracted from available space in swap/home calculations
|
||||
partitioning_disk_overhead_gb: 20
|
||||
|
||||
# CIS-required reserved space for /var, /var/log, /var/log/audit, /home
|
||||
partitioning_cis_reserved_gb: 7.5
|
||||
|
||||
# Home allocation: percentage of (disk - overhead), bounded by min/max
|
||||
partitioning_home_allocation_pct: 0.1
|
||||
partitioning_home_min_gb: 2
|
||||
partitioning_home_max_gb: 20
|
||||
|
||||
# Btrfs home quota (applied when CIS is enabled)
|
||||
partitioning_btrfs_home_quota: 2G
|
||||
partitioning_separate_boot: >-
|
||||
{{
|
||||
(
|
||||
(system_cfg.luks.enabled | bool)
|
||||
or (system_cfg.filesystem != 'btrfs')
|
||||
)
|
||||
and (os not in ['archlinux'])
|
||||
and ((os | default('')) not in ['archlinux'])
|
||||
}}
|
||||
partitioning_boot_fs_fstype: >-
|
||||
{{
|
||||
system_cfg.filesystem
|
||||
if system_cfg.filesystem != 'btrfs'
|
||||
else ('xfs' if is_rhel else 'ext4')
|
||||
else ('xfs' if (is_rhel | default(false) | bool) else 'ext4')
|
||||
}}
|
||||
partitioning_boot_fs_partition_suffix: >-
|
||||
{{
|
||||
@@ -37,7 +57,7 @@ partitioning_efi_mountpoint: >-
|
||||
if (partitioning_separate_boot | bool)
|
||||
else (
|
||||
'/boot/efi'
|
||||
if is_rhel or (os in ['ubuntu', 'ubuntu-lts'] or (os == 'debian' and (os_version | string) in ['11', '12', '13']))
|
||||
if (is_rhel | default(false) | bool) or ((os | default('')) in ['ubuntu', 'ubuntu-lts'] or ((os | default('')) == 'debian' and (os_version | default('') | string) in ['11', '12', '13']))
|
||||
else '/boot'
|
||||
)
|
||||
}}
|
||||
|
||||
@@ -54,13 +54,17 @@
|
||||
- { subvol: pkg }
|
||||
- { subvol: var_log }
|
||||
- { subvol: var_log_audit }
|
||||
loop_control:
|
||||
label: "{{ item.subvol }}"
|
||||
register: partitioning_btrfs_subvol_result
|
||||
|
||||
- name: Set quotas for subvolumes
|
||||
when: system_cfg.features.cis.enabled
|
||||
ansible.builtin.command: btrfs qgroup limit {{ item.quota }} /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
|
||||
loop:
|
||||
- { subvol: home, quota: 2G }
|
||||
- { subvol: home, quota: "{{ partitioning_btrfs_home_quota }}" }
|
||||
loop_control:
|
||||
label: "{{ item.subvol }}"
|
||||
register: partitioning_btrfs_qgroup_result
|
||||
changed_when: false
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
- name: Create and format ext4 logical volumes
|
||||
when: system_cfg.features.cis.enabled or item.lv not in ['home', 'var', 'var_log', 'var_log_audit']
|
||||
community.general.filesystem:
|
||||
dev: /dev/sys/{{ item.lv }}
|
||||
dev: /dev/{{ partitioning_vg_name }}/{{ item.lv }}
|
||||
fstype: ext4
|
||||
force: true
|
||||
loop:
|
||||
@@ -11,17 +11,21 @@
|
||||
- { lv: var }
|
||||
- { lv: var_log }
|
||||
- { lv: var_log_audit }
|
||||
loop_control:
|
||||
label: "{{ item.lv }}"
|
||||
|
||||
- name: Remove Unsupported features for older Systems
|
||||
when: >
|
||||
(os in ['almalinux', 'rocky', 'rhel'] or (os == 'debian' and (os_version | string) == '11'))
|
||||
and (system_cfg.features.cis.enabled or item.lv not in ['home', 'var', 'var_log', 'var_log_audit'])
|
||||
ansible.builtin.command: tune2fs -O "^orphan_file,^metadata_csum_seed" "/dev/sys/{{ item.lv }}"
|
||||
ansible.builtin.command: tune2fs -O "^orphan_file,^metadata_csum_seed" "/dev/{{ partitioning_vg_name }}/{{ item.lv }}"
|
||||
loop:
|
||||
- { lv: root }
|
||||
- { lv: home }
|
||||
- { lv: var }
|
||||
- { lv: var_log }
|
||||
- { lv: var_log_audit }
|
||||
loop_control:
|
||||
label: "{{ item.lv }}"
|
||||
register: partitioning_ext4_tune_result
|
||||
changed_when: partitioning_ext4_tune_result.rc == 0
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
| selectattr('mount.path')
|
||||
| list
|
||||
}}
|
||||
changed_when: false
|
||||
|
||||
- name: Validate additional disks do not target install_drive
|
||||
when: partitioning_extra_disks | length > 0
|
||||
@@ -59,7 +58,16 @@
|
||||
dev: "{{ item.partition }}"
|
||||
fstype: "{{ item.mount.fstype }}"
|
||||
opts: "{{ _all_opts }}"
|
||||
force: true
|
||||
loop: "{{ partitioning_extra_disks }}"
|
||||
loop_control:
|
||||
label: "{{ item.partition }}"
|
||||
|
||||
- name: Collect extra disk UUIDs
|
||||
when: partitioning_extra_disks | length > 0
|
||||
ansible.builtin.command: "blkid -s UUID -o value {{ item.partition }}"
|
||||
register: partitioning_extra_disk_uuids
|
||||
changed_when: false
|
||||
failed_when: partitioning_extra_disk_uuids.rc != 0 or (partitioning_extra_disk_uuids.stdout | trim | length) == 0
|
||||
loop: "{{ partitioning_extra_disks }}"
|
||||
loop_control:
|
||||
label: "{{ item.partition }}"
|
||||
@@ -79,11 +87,11 @@
|
||||
- name: Mount additional disks for fstab generation
|
||||
when: partitioning_extra_disks | length > 0
|
||||
ansible.posix.mount:
|
||||
path: "/mnt{{ item.mount.path }}"
|
||||
src: "{{ item.partition }}"
|
||||
fstype: "{{ item.mount.fstype }}"
|
||||
opts: "{{ item.mount.opts | default('defaults') }}"
|
||||
path: "/mnt{{ item.0.mount.path }}"
|
||||
src: "UUID={{ item.1.stdout }}"
|
||||
fstype: "{{ item.0.mount.fstype }}"
|
||||
opts: "{{ item.0.mount.opts | default('defaults') }}"
|
||||
state: mounted
|
||||
loop: "{{ partitioning_extra_disks }}"
|
||||
loop: "{{ partitioning_extra_disks | zip(partitioning_extra_disk_uuids.results) | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.mount.path }}"
|
||||
label: "{{ item.0.mount.path }}"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
when:
|
||||
- system_cfg.features.swap.enabled | bool
|
||||
- partitioning_vm_memory is not defined or (partitioning_vm_memory | float) <= 0
|
||||
- system_cfg is not defined or (system_cfg.memory | default(0) | float) <= 0
|
||||
- (system_cfg.memory | default(0) | float) <= 0
|
||||
block:
|
||||
- name: Read system memory
|
||||
ansible.builtin.command: awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
- name: Remove LVM volume group
|
||||
community.general.lvg:
|
||||
vg: sys
|
||||
vg: "{{ partitioning_vg_name }}"
|
||||
state: absent
|
||||
force: true
|
||||
failed_when: false
|
||||
@@ -93,10 +93,10 @@
|
||||
failed_when: false
|
||||
|
||||
- name: Wipe filesystem signatures
|
||||
ansible.builtin.command: >-
|
||||
ansible.builtin.shell: >-
|
||||
find /dev -wholename "{{ install_drive }}*" -exec wipefs --force --all {} \;
|
||||
register: partitioning_wipefs_result
|
||||
changed_when: false
|
||||
changed_when: partitioning_wipefs_result.rc == 0
|
||||
failed_when: false
|
||||
|
||||
- name: Refresh kernel partition table
|
||||
@@ -122,6 +122,8 @@
|
||||
flags: "{{ item.flags | default(omit) }}"
|
||||
state: present
|
||||
loop: "{{ partitioning_layout }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
rescue:
|
||||
- name: Refresh kernel partition table after failure
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
@@ -144,6 +146,8 @@
|
||||
flags: "{{ item.flags | default(omit) }}"
|
||||
state: present
|
||||
loop: "{{ partitioning_layout }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Settle partition table
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
@@ -251,7 +255,7 @@
|
||||
block:
|
||||
- name: Create LVM volume group
|
||||
community.general.lvg:
|
||||
vg: sys
|
||||
vg: "{{ partitioning_vg_name }}"
|
||||
pvs: "{{ partitioning_root_device }}"
|
||||
|
||||
- name: Create LVM logical volumes
|
||||
@@ -272,10 +276,11 @@
|
||||
partitioning_lvm_swap_target_gb: >-
|
||||
{{
|
||||
(
|
||||
[
|
||||
(partitioning_memory_mb | float / 1024),
|
||||
4
|
||||
] | max | float
|
||||
((partitioning_memory_mb | float / 1024) >= 16.0)
|
||||
| ternary(
|
||||
(partitioning_memory_mb | float / 2048),
|
||||
[(partitioning_memory_mb | float / 1024), 4] | max | float
|
||||
)
|
||||
)
|
||||
if system_cfg.features.swap.enabled | bool
|
||||
else 0
|
||||
@@ -285,7 +290,7 @@
|
||||
(
|
||||
4
|
||||
+ [
|
||||
(partitioning_disk_size_gb | float) - 20,
|
||||
(partitioning_disk_size_gb | float) - (partitioning_disk_overhead_gb | float),
|
||||
0
|
||||
] | max
|
||||
)
|
||||
@@ -310,7 +315,7 @@
|
||||
(
|
||||
(partitioning_disk_size_gb | float)
|
||||
- (partitioning_reserved_gb | float)
|
||||
- (system_cfg.features.cis.enabled | ternary(7.5, 0))
|
||||
- (system_cfg.features.cis.enabled | ternary(partitioning_cis_reserved_gb | float, 0))
|
||||
- partitioning_lvm_extent_reserve_gb
|
||||
- 4
|
||||
),
|
||||
@@ -325,14 +330,22 @@
|
||||
(
|
||||
(partitioning_disk_size_gb | float)
|
||||
- (partitioning_reserved_gb | float)
|
||||
- (system_cfg.features.cis.enabled | ternary(7.5, 0))
|
||||
- (system_cfg.features.cis.enabled | ternary(partitioning_cis_reserved_gb | float, 0))
|
||||
- partitioning_lvm_extent_reserve_gb
|
||||
- partitioning_lvm_swap_target_limited_gb
|
||||
) | float
|
||||
}}
|
||||
partitioning_lvm_home_raw_gb: >-
|
||||
{{
|
||||
((partitioning_disk_size_gb | float) - (partitioning_disk_overhead_gb | float))
|
||||
* (partitioning_home_allocation_pct | float)
|
||||
}}
|
||||
partitioning_lvm_home_gb: >-
|
||||
{{
|
||||
([([(((partitioning_disk_size_gb | float) - 20) * 0.1), 2] | max), 20] | min)
|
||||
[
|
||||
[(partitioning_lvm_home_raw_gb | float), (partitioning_home_min_gb | float)] | max,
|
||||
(partitioning_home_max_gb | float)
|
||||
] | min
|
||||
}}
|
||||
partitioning_lvm_root_default_gb: >-
|
||||
{{
|
||||
@@ -374,7 +387,10 @@
|
||||
- (partitioning_lvm_swap_gb | float)
|
||||
- partitioning_lvm_extent_reserve_gb
|
||||
- (
|
||||
(partitioning_lvm_home_gb | float) + 5.5
|
||||
(partitioning_lvm_home_gb | float)
|
||||
+ (partitioning_lvm_var_gb | float)
|
||||
+ (partitioning_lvm_var_log_gb | float)
|
||||
+ (partitioning_lvm_var_log_audit_gb | float)
|
||||
if system_cfg.features.cis.enabled
|
||||
else 0
|
||||
)
|
||||
@@ -389,7 +405,7 @@
|
||||
else partitioning_lvm_root_default_gb
|
||||
}}
|
||||
community.general.lvol:
|
||||
vg: sys
|
||||
vg: "{{ partitioning_vg_name }}"
|
||||
lv: "{{ item.lv }}"
|
||||
size: "{{ item.size }}"
|
||||
state: present
|
||||
@@ -400,9 +416,11 @@
|
||||
size: "{{ partitioning_lvm_swap_gb | string + 'G' }}"
|
||||
- lv: home
|
||||
size: "{{ partitioning_lvm_home_gb | string + 'G' }}"
|
||||
- { lv: var, size: "2G" }
|
||||
- { lv: var_log, size: "2G" }
|
||||
- { lv: var_log_audit, size: "1.5G" }
|
||||
- { lv: var, size: "{{ partitioning_lvm_var_gb }}G" }
|
||||
- { lv: var_log, size: "{{ partitioning_lvm_var_log_gb }}G" }
|
||||
- { lv: var_log_audit, size: "{{ partitioning_lvm_var_log_audit_gb }}G" }
|
||||
loop_control:
|
||||
label: "{{ item.lv }}"
|
||||
|
||||
- name: Create filesystems
|
||||
block:
|
||||
@@ -438,7 +456,7 @@
|
||||
- system_cfg.features.swap.enabled | bool
|
||||
community.general.filesystem:
|
||||
fstype: swap
|
||||
dev: /dev/sys/swap
|
||||
dev: /dev/{{ partitioning_vg_name }}/swap
|
||||
|
||||
- name: Create filesystem
|
||||
ansible.builtin.include_tasks: "{{ system_cfg.filesystem }}.yml"
|
||||
@@ -447,6 +465,7 @@
|
||||
ansible.builtin.command: blkid -s UUID -o value '{{ install_drive }}{{ partitioning_boot_partition_suffix }}'
|
||||
register: partitioning_boot_uuid
|
||||
changed_when: false
|
||||
failed_when: partitioning_boot_uuid.rc != 0 or (partitioning_boot_uuid.stdout | trim | length) == 0
|
||||
|
||||
- name: Get UUID for /boot filesystem
|
||||
when: partitioning_separate_boot | bool
|
||||
@@ -454,57 +473,65 @@
|
||||
blkid -s UUID -o value '{{ install_drive }}{{ partitioning_boot_fs_partition_suffix }}'
|
||||
register: partitioning_boot_fs_uuid
|
||||
changed_when: false
|
||||
failed_when: partitioning_boot_fs_uuid.rc != 0 or (partitioning_boot_fs_uuid.stdout | trim | length) == 0
|
||||
|
||||
- name: Get UUID for main filesystem
|
||||
ansible.builtin.command: blkid -s UUID -o value '{{ partitioning_root_device }}'
|
||||
register: partitioning_main_uuid
|
||||
changed_when: false
|
||||
failed_when: partitioning_main_uuid.rc != 0 or (partitioning_main_uuid.stdout | trim | length) == 0
|
||||
|
||||
- name: Get UUID for LVM root filesystem
|
||||
when: system_cfg.filesystem != 'btrfs'
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/sys/root
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/root
|
||||
register: partitioning_uuid_root_result
|
||||
changed_when: false
|
||||
failed_when: partitioning_uuid_root_result.rc != 0 or (partitioning_uuid_root_result.stdout | trim | length) == 0
|
||||
|
||||
- name: Get UUID for LVM swap filesystem
|
||||
when:
|
||||
- system_cfg.filesystem != 'btrfs'
|
||||
- system_cfg.features.swap.enabled | bool
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/sys/swap
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/swap
|
||||
register: partitioning_uuid_swap_result
|
||||
changed_when: false
|
||||
failed_when: partitioning_uuid_swap_result.rc != 0 or (partitioning_uuid_swap_result.stdout | trim | length) == 0
|
||||
|
||||
- name: Get UUID for LVM home filesystem
|
||||
when:
|
||||
- system_cfg.filesystem != 'btrfs'
|
||||
- system_cfg.features.cis.enabled
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/sys/home
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/home
|
||||
register: partitioning_uuid_home_result
|
||||
changed_when: false
|
||||
failed_when: partitioning_uuid_home_result.rc != 0 or (partitioning_uuid_home_result.stdout | trim | length) == 0
|
||||
|
||||
- name: Get UUID for LVM var filesystem
|
||||
when:
|
||||
- system_cfg.filesystem != 'btrfs'
|
||||
- system_cfg.features.cis.enabled
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/sys/var
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/var
|
||||
register: partitioning_uuid_var_result
|
||||
changed_when: false
|
||||
failed_when: partitioning_uuid_var_result.rc != 0 or (partitioning_uuid_var_result.stdout | trim | length) == 0
|
||||
|
||||
- name: Get UUID for LVM var_log filesystem
|
||||
when:
|
||||
- system_cfg.filesystem != 'btrfs'
|
||||
- system_cfg.features.cis.enabled
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/sys/var_log
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/var_log
|
||||
register: partitioning_uuid_var_log_result
|
||||
changed_when: false
|
||||
failed_when: partitioning_uuid_var_log_result.rc != 0 or (partitioning_uuid_var_log_result.stdout | trim | length) == 0
|
||||
|
||||
- name: Get UUID for LVM var_log_audit filesystem
|
||||
when:
|
||||
- system_cfg.filesystem != 'btrfs'
|
||||
- system_cfg.features.cis.enabled
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/sys/var_log_audit
|
||||
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/var_log_audit
|
||||
register: partitioning_uuid_var_log_audit_result
|
||||
changed_when: false
|
||||
failed_when: partitioning_uuid_var_log_audit_result.rc != 0 or (partitioning_uuid_var_log_audit_result.stdout | trim | length) == 0
|
||||
|
||||
- name: Assign UUIDs to Variables
|
||||
when: system_cfg.filesystem != 'btrfs'
|
||||
@@ -514,31 +541,31 @@
|
||||
{{
|
||||
partitioning_uuid_swap_result.stdout_lines | default([])
|
||||
if system_cfg.features.swap.enabled | bool
|
||||
else ''
|
||||
else []
|
||||
}}
|
||||
partitioning_uuid_home: >-
|
||||
{{
|
||||
partitioning_uuid_home_result.stdout_lines | default([])
|
||||
if system_cfg.features.cis.enabled
|
||||
else ''
|
||||
else []
|
||||
}}
|
||||
partitioning_uuid_var: >-
|
||||
{{
|
||||
partitioning_uuid_var_result.stdout_lines | default([])
|
||||
if system_cfg.features.cis.enabled
|
||||
else ''
|
||||
else []
|
||||
}}
|
||||
partitioning_uuid_var_log: >-
|
||||
{{
|
||||
partitioning_uuid_var_log_result.stdout_lines | default([])
|
||||
if system_cfg.features.cis.enabled
|
||||
else ''
|
||||
else []
|
||||
}}
|
||||
partitioning_uuid_var_log_audit: >-
|
||||
{{
|
||||
partitioning_uuid_var_log_audit_result.stdout_lines | default([])
|
||||
if system_cfg.features.cis.enabled
|
||||
else ''
|
||||
else []
|
||||
}}
|
||||
|
||||
- name: Mount filesystems
|
||||
@@ -562,6 +589,7 @@
|
||||
opts: "{{ item.opts }}"
|
||||
state: mounted
|
||||
loop:
|
||||
# ssd: no-op on kernels 5.15+ (btrfs auto-detects); kept for older kernel compat
|
||||
- path: ""
|
||||
uuid: "{{ partitioning_uuid_root[0] | default(omit) }}"
|
||||
opts: >-
|
||||
@@ -636,6 +664,8 @@
|
||||
'ssd', 'space_cache=v2', 'discard=async', 'subvol=@var_log_audit'
|
||||
] | reject('equalto', '') | join(',')
|
||||
}}
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Mount /boot filesystem
|
||||
when: partitioning_separate_boot | bool
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
- name: Create and format XFS logical volumes
|
||||
when: system_cfg.features.cis.enabled or item.lv not in ['home', 'var', 'var_log', 'var_log_audit']
|
||||
community.general.filesystem:
|
||||
dev: /dev/sys/{{ item.lv }}
|
||||
dev: /dev/{{ partitioning_vg_name }}/{{ item.lv }}
|
||||
fstype: xfs
|
||||
opts: "{{ '-m bigtime=0 -i nrext64=0,exchange=0 -n parent=0' if is_rhel | bool else omit }}"
|
||||
force: true
|
||||
@@ -12,3 +12,5 @@
|
||||
- { lv: var }
|
||||
- { lv: var_log }
|
||||
- { lv: var_log_audit }
|
||||
loop_control:
|
||||
label: "{{ item.lv }}"
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
---
|
||||
- name: Physical install safety confirmation
|
||||
when: system_cfg.type == "physical"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- physical_install_confirmed | default(false) | bool
|
||||
fail_msg: >-
|
||||
DANGER: Physical install will WIPE {{ install_drive }} on {{ inventory_hostname }}.
|
||||
Set physical_install_confirmed=true in inventory to proceed.
|
||||
quiet: true
|
||||
|
||||
- name: VM existence protection check
|
||||
when: system_cfg.type == "virtual"
|
||||
block:
|
||||
@@ -23,23 +33,27 @@
|
||||
Please choose a different hostname or remove the existing VM manually before proceeding.
|
||||
quiet: true
|
||||
|
||||
- name: Check if VM already exists on Proxmox
|
||||
- name: Check VM existence on Proxmox
|
||||
when: hypervisor_type == "proxmox"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
module_defaults:
|
||||
community.proxmox.proxmox_vm_info:
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password }}"
|
||||
block:
|
||||
- name: Query Proxmox for existing VM
|
||||
community.proxmox.proxmox_vm_info:
|
||||
node: "{{ hypervisor_cfg.host }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
name: "{{ hostname }}"
|
||||
type: qemu
|
||||
register: system_check_proxmox_check_result
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Abort if VM already exists on Proxmox
|
||||
when: hypervisor_type == "proxmox"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system_check_proxmox_check_result.proxmox_vms | default([]) | length == 0
|
||||
@@ -49,23 +63,27 @@
|
||||
Please choose a different hostname or VM ID, or remove the existing VM manually before proceeding.
|
||||
quiet: true
|
||||
|
||||
- name: Check if VM already exists in vCenter
|
||||
- name: Check VM existence in vCenter
|
||||
when: hypervisor_type == "vmware"
|
||||
delegate_to: localhost
|
||||
module_defaults:
|
||||
community.vmware.vmware_guest_info:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
block:
|
||||
- name: Query vCenter for existing VM
|
||||
community.vmware.vmware_guest_info:
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
name: "{{ hostname }}"
|
||||
folder: "{{ system_cfg.path if system_cfg.path | length > 0 else omit }}"
|
||||
register: system_check_vmware_check_result
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Fail if vCenter lookup failed unexpectedly
|
||||
when: hypervisor_type == "vmware"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- not system_check_vmware_check_result.failed
|
||||
@@ -76,7 +94,6 @@
|
||||
quiet: true
|
||||
|
||||
- name: Abort if VM already exists in vCenter
|
||||
when: hypervisor_type == "vmware"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system_check_vmware_check_result.instance is not defined
|
||||
|
||||
@@ -11,6 +11,10 @@ virtualization_libvirt_cloudinit_path: >-
|
||||
{{ [virtualization_libvirt_image_dir, hostname ~ '-cloudinit.iso'] | ansible.builtin.path_join }}
|
||||
virtualization_xen_disk_path: /var/lib/xen/images
|
||||
|
||||
virtualization_libvirt_machine_type: q35
|
||||
virtualization_libvirt_ovmf_code: /usr/share/edk2/x64/OVMF_CODE.secboot.4m.fd
|
||||
virtualization_libvirt_ovmf_vars: /usr/share/edk2/x64/OVMF_VARS.4m.fd
|
||||
|
||||
virtualization_tpm2_enabled: >-
|
||||
{{
|
||||
(system_cfg.luks.enabled | bool)
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
loop_control:
|
||||
label: "{{ item | to_json }}"
|
||||
extended: true
|
||||
changed_when: false
|
||||
|
||||
- name: Create VM disks
|
||||
delegate_to: localhost
|
||||
@@ -61,6 +60,7 @@
|
||||
- "/tmp/cloud-network-config-{{ hostname }}.yml"
|
||||
creates: "{{ virtualization_libvirt_cloudinit_path }}"
|
||||
|
||||
# uri defaults to qemu:///system (local libvirtd)
|
||||
- name: Create VM using libvirt
|
||||
delegate_to: localhost
|
||||
community.libvirt.virt:
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
---
|
||||
- name: Deploy VM on Proxmox
|
||||
delegate_to: localhost
|
||||
module_defaults:
|
||||
community.proxmox.proxmox_kvm:
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password }}"
|
||||
node: "{{ hypervisor_cfg.host }}"
|
||||
block:
|
||||
- name: Create VM on Proxmox
|
||||
vars:
|
||||
virtualization_proxmox_scsi: >-
|
||||
{%- set out = {} -%}
|
||||
@@ -31,13 +39,9 @@
|
||||
{%- endfor -%}
|
||||
{{ out }}
|
||||
community.proxmox.proxmox_kvm:
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password }}"
|
||||
ciuser: "{{ system_cfg.users[0].name }}"
|
||||
cipassword: "{{ system_cfg.users[0].password }}"
|
||||
ciupgrade: false
|
||||
node: "{{ hypervisor_cfg.host }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
name: "{{ hostname }}"
|
||||
cpu: host
|
||||
@@ -74,20 +78,17 @@
|
||||
searchdomains: "{{ system_cfg.network.dns.search if system_cfg.network.dns.search | length else omit }}"
|
||||
onboot: true
|
||||
state: present
|
||||
no_log: true
|
||||
|
||||
- name: Start VM on Proxmox
|
||||
delegate_to: localhost
|
||||
- name: Start VM on Proxmox
|
||||
community.proxmox.proxmox_kvm:
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password }}"
|
||||
node: "{{ hypervisor_cfg.host }}"
|
||||
name: "{{ hostname }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
state: started
|
||||
no_log: true
|
||||
register: virtualization_proxmox_start_result
|
||||
|
||||
- name: Set VM created fact
|
||||
- name: Set VM created fact
|
||||
ansible.builtin.set_fact:
|
||||
virtualization_vm_created_in_run: true
|
||||
when: virtualization_proxmox_start_result is defined and virtualization_proxmox_start_result.changed | bool
|
||||
|
||||
@@ -10,10 +10,31 @@
|
||||
loop: "{{ system_cfg.disks }}"
|
||||
loop_control:
|
||||
label: "{{ item | to_json }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Create VM in vCenter
|
||||
- name: Deploy VM in vCenter
|
||||
delegate_to: localhost
|
||||
module_defaults:
|
||||
community.vmware.vmware_guest:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
community.vmware.vmware_guest_tpm:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
vmware.vmware.vm_powerstate:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
block:
|
||||
# community.vmware: full-featured guest management
|
||||
- name: Create VM in vCenter
|
||||
vars:
|
||||
virtualization_vmware_networks: >-
|
||||
{%- set ns = namespace(out=[]) -%}
|
||||
@@ -26,14 +47,10 @@
|
||||
{%- endfor -%}
|
||||
{{ ns.out }}
|
||||
community.vmware.vmware_guest:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
cluster: "{{ hypervisor_cfg.cluster }}"
|
||||
folder: "{{ system_cfg.path if system_cfg.path | string | length > 0 else omit }}"
|
||||
name: "{{ hostname }}"
|
||||
# Generic guest ID — VMware auto-detects OS post-install
|
||||
guest_id: otherLinux64Guest
|
||||
annotation: |
|
||||
{{ note if note is defined else '' }}
|
||||
@@ -65,9 +82,10 @@
|
||||
} ] if rhel_iso is defined and rhel_iso | length > 0 else [] )
|
||||
}}
|
||||
networks: "{{ virtualization_vmware_networks }}"
|
||||
no_log: true
|
||||
register: virtualization_vmware_create_result
|
||||
|
||||
- name: Set VM created fact when VM was powered on during creation
|
||||
- name: Set VM created fact when VM was powered on during creation
|
||||
ansible.builtin.set_fact:
|
||||
virtualization_vm_created_in_run: true
|
||||
when:
|
||||
@@ -75,33 +93,24 @@
|
||||
- not virtualization_tpm2_enabled | bool
|
||||
- virtualization_vmware_create_result.changed | bool
|
||||
|
||||
- name: Ensure vTPM2 is enabled when required
|
||||
- name: Ensure vTPM2 is enabled when required
|
||||
when: virtualization_tpm2_enabled | bool
|
||||
delegate_to: localhost
|
||||
community.vmware.vmware_guest_tpm:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
folder: "{{ system_cfg.path if system_cfg.path | string | length > 0 else omit }}"
|
||||
name: "{{ hostname }}"
|
||||
state: present
|
||||
no_log: true
|
||||
|
||||
- name: Start VM in vCenter
|
||||
# vmware.vmware: modern collection for power operations
|
||||
- name: Start VM in vCenter
|
||||
when: virtualization_tpm2_enabled | bool
|
||||
delegate_to: localhost
|
||||
vmware.vmware.vm_powerstate:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
name: "{{ hostname }}"
|
||||
state: powered-on
|
||||
no_log: true
|
||||
register: virtualization_vmware_start_result
|
||||
|
||||
- name: Set VM created fact when VM was started separately (TPM2 case)
|
||||
- name: Set VM created fact when VM was started separately (TPM2 case)
|
||||
ansible.builtin.set_fact:
|
||||
virtualization_vm_created_in_run: true
|
||||
when:
|
||||
|
||||
@@ -49,6 +49,16 @@
|
||||
dest: /tmp/xen-{{ hostname }}.cfg
|
||||
mode: "0644"
|
||||
|
||||
- name: Check if Xen VM already exists
|
||||
delegate_to: localhost
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- xl
|
||||
- list
|
||||
register: virtualization_xen_pre_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Create Xen VM
|
||||
delegate_to: localhost
|
||||
ansible.builtin.command:
|
||||
@@ -58,8 +68,11 @@
|
||||
- /tmp/xen-{{ hostname }}.cfg
|
||||
register: virtualization_xen_create_result
|
||||
changed_when: virtualization_xen_create_result.rc == 0
|
||||
when: >-
|
||||
not (virtualization_xen_pre_check.stdout | default('')
|
||||
is search('(?m)^' ~ (hostname | ansible.builtin.regex_escape) ~ '\\s+\\d+\\s'))
|
||||
|
||||
- name: Ensure VM is running
|
||||
- name: Verify VM is running
|
||||
delegate_to: localhost
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
@@ -67,13 +80,10 @@
|
||||
- list
|
||||
register: virtualization_xen_list_result
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
failed_when: >-
|
||||
not (virtualization_xen_list_result.stdout | default('')
|
||||
is search('(?m)^' ~ (hostname | ansible.builtin.regex_escape) ~ '\\s+\\d+\\s'))
|
||||
|
||||
- name: Set VM created fact
|
||||
ansible.builtin.set_fact:
|
||||
virtualization_vm_created_in_run: true
|
||||
when:
|
||||
- virtualization_xen_list_result is defined
|
||||
- >-
|
||||
virtualization_xen_list_result.stdout | default('')
|
||||
is search('(?m)^' ~ (hostname | ansible.builtin.regex_escape) ~ '\\s+\\d+\\s')
|
||||
virtualization_vm_created_in_run: "{{ (virtualization_xen_create_result.rc | default(1)) == 0 }}"
|
||||
|
||||
@@ -13,7 +13,9 @@ network:
|
||||
addresses:
|
||||
- "{{ iface.ip }}/{{ iface.prefix }}"
|
||||
{% if iface.gateway | default('') | string | length %}
|
||||
gateway4: "{{ iface.gateway }}"
|
||||
routes:
|
||||
- to: default
|
||||
via: "{{ iface.gateway }}"
|
||||
{% endif %}
|
||||
{% else %}
|
||||
dhcp4: true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#cloud-config
|
||||
hostname: "archiso"
|
||||
hostname: "{{ hostname }}"
|
||||
ssh_pwauth: true
|
||||
package_update: false
|
||||
package_upgrade: false
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<domain type='kvm'>
|
||||
<name>{{ hostname }}</name>
|
||||
<memory>{{ system_cfg.memory | int * 1024 }}</memory>
|
||||
{% if system_cfg.balloon is defined and system_cfg.balloon | int > 0 %}<currentMemory>{{ system_cfg.balloon | int * 1024 }}</currentMemory>{% endif %}
|
||||
<memory unit='KiB'>{{ system_cfg.memory | int * 1024 }}</memory>
|
||||
{% if system_cfg.balloon is defined and system_cfg.balloon | int > 0 %}<currentMemory unit='KiB'>{{ system_cfg.balloon | int * 1024 }}</currentMemory>{% endif %}
|
||||
<vcpu placement='static'>{{ system_cfg.cpus }}</vcpu>
|
||||
<os>
|
||||
<type arch='x86_64' machine="pc-q35-8.0">hvm</type>
|
||||
<type arch='x86_64' machine="{{ virtualization_libvirt_machine_type }}">hvm</type>
|
||||
<bootmenu enable='no'/>
|
||||
<boot dev='hd'/>
|
||||
<boot dev='cdrom'/>
|
||||
<loader readonly="yes" type="pflash">/usr/share/edk2/x64/OVMF_CODE.secboot.4m.fd</loader>
|
||||
<nvram template="/usr/share/edk2/x64/OVMF_VARS.4m.fd"/>
|
||||
<loader readonly="yes" type="pflash">{{ virtualization_libvirt_ovmf_code }}</loader>
|
||||
<nvram template="{{ virtualization_libvirt_ovmf_vars }}"/>
|
||||
</os>
|
||||
<features>
|
||||
<acpi/>
|
||||
@@ -33,17 +33,20 @@
|
||||
<driver name="qemu" type="raw"/>
|
||||
<source file="{{ boot_iso }}"/>
|
||||
<target dev="sda" bus="sata"/>
|
||||
<readonly/>
|
||||
</disk>
|
||||
<disk type="file" device="cdrom">
|
||||
<driver name="qemu" type="raw"/>
|
||||
<source file="{{ virtualization_libvirt_cloudinit_path }}"/>
|
||||
<target dev="sdb" bus="sata"/>
|
||||
<readonly/>
|
||||
</disk>
|
||||
{% if rhel_iso is defined and rhel_iso | length > 0 %}
|
||||
<disk type="file" device="cdrom">
|
||||
<driver name="qemu" type="raw"/>
|
||||
<source file="{{ rhel_iso }}"/>
|
||||
<target dev="sdc" bus="sata"/>
|
||||
<readonly/>
|
||||
</disk>
|
||||
{% endif %}
|
||||
{% for iface in system_cfg.network.interfaces %}
|
||||
|
||||
@@ -12,7 +12,7 @@ disk = [
|
||||
]
|
||||
vif = [
|
||||
{%- for iface in system_cfg.network.interfaces -%}
|
||||
'bridge={{ iface.bridge }},model=e1000'{% if not loop.last %}, {% endif %}
|
||||
'bridge={{ iface.bridge }},model=virtio'{% if not loop.last %}, {% endif %}
|
||||
{%- endfor -%}
|
||||
]
|
||||
boot = "{{ 'dc' if xen_installer_media_enabled | bool else 'c' }}"
|
||||
Reference in New Issue
Block a user