diff --git a/roles/environment/tasks/_configure_network.yml b/roles/environment/tasks/_configure_network.yml new file mode 100644 index 0000000..4361f9f --- /dev/null +++ b/roles/environment/tasks/_configure_network.yml @@ -0,0 +1,64 @@ +--- +- name: Select primary Network Interface + when: hypervisor_type == "vmware" + ansible.builtin.set_fact: + environment_interface_name: >- + {{ + ( + (ansible_facts.interfaces | default(ansible_facts['ansible_interfaces'] | default([]))) + | reject('equalto', 'lo') + | list + | first + ) + | default('') + }} + +- name: Set IP-Address + when: + - hypervisor_type == "vmware" + - system_cfg.network.ip is defined and system_cfg.network.ip | string | length > 0 + ansible.builtin.command: >- + ip addr replace {{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }} + dev {{ environment_interface_name }} + register: environment_ip_result + changed_when: environment_ip_result.rc == 0 + +- name: Set Default Gateway + when: + - hypervisor_type == "vmware" + - system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length > 0 + - system_cfg.network.ip is defined and system_cfg.network.ip | string | length > 0 + ansible.builtin.command: "ip route replace default via {{ system_cfg.network.gateway }}" + register: environment_gateway_result + changed_when: environment_gateway_result.rc == 0 + +- name: Synchronize clock via NTP + ansible.builtin.command: timedatectl set-ntp true + register: environment_ntp_result + changed_when: environment_ntp_result.rc == 0 + +- name: Configure SSH for root login + when: hypervisor_type == "vmware" and hypervisor_cfg.ssh | bool + block: + - name: Allow login + ansible.builtin.replace: + path: /etc/ssh/sshd_config + regexp: "{{ item.regexp }}" + replace: "{{ item.replace }}" + loop: + - regexp: "^#?PermitEmptyPasswords.*" + replace: "PermitEmptyPasswords yes" + - regexp: "^#?PermitRootLogin.*" + replace: "PermitRootLogin yes" + loop_control: + label: "{{ item.replace }}" + + - name: Reload SSH service to apply changes + ansible.builtin.service: + name: sshd + state: reloaded + + - name: Set SSH connection for VMware + ansible.builtin.set_fact: + ansible_connection: ssh + ansible_user: root diff --git a/roles/environment/tasks/_detect_live.yml b/roles/environment/tasks/_detect_live.yml new file mode 100644 index 0000000..ae15d89 --- /dev/null +++ b/roles/environment/tasks/_detect_live.yml @@ -0,0 +1,76 @@ +--- +- name: Wait for connection + ansible.builtin.wait_for_connection: + timeout: 180 + delay: 5 + +- name: Gather facts + ansible.builtin.setup: + +- name: Check for live environment markers + ansible.builtin.stat: + path: "{{ item }}" + loop: + - /run/archiso + - /run/live + - /run/initramfs + - /run/initramfs/live + register: environment_live_marker_stat + changed_when: false + +- name: Determine root filesystem type + ansible.builtin.set_fact: + environment_root_fstype: >- + {{ + ansible_mounts + | selectattr('mount', 'equalto', '/') + | map(attribute='fstype') + | list + | first + | default('') + | lower + }} + environment_archiso_present: >- + {{ + ( + environment_live_marker_stat.results + | selectattr('item', 'equalto', '/run/archiso') + | selectattr('stat.exists') + | list + | length + ) > 0 + }} + +- name: Identify live environment indicators + ansible.builtin.set_fact: + environment_is_live_environment: >- + {{ + ( + environment_live_marker_stat.results + | selectattr('stat.exists') + | list + | length + ) > 0 + or environment_root_fstype in ['overlay', 'overlayfs', 'squashfs', 'aufs'] + or (ansible_hostname | default('') | lower is search('live')) + }} + +- name: Abort if target is not a live environment + ansible.builtin.assert: + that: + - environment_is_live_environment | bool + fail_msg: | + PRODUCTION SYSTEM DETECTED - ABORTING + + The target system does not appear to be a live installer environment. + This playbook must run from a live ISO to avoid wiping production data. + + Boot from a live installer (Arch, Debian, Ubuntu, etc.) and retry. + quiet: true + +- name: Abort if the host is not booted from the Arch install media + when: + - not (custom_iso | bool) + - not environment_archiso_present | bool + ansible.builtin.fail: + msg: This host is not booted from the Arch install media! diff --git a/roles/environment/tasks/_prepare_installer.yml b/roles/environment/tasks/_prepare_installer.yml new file mode 100644 index 0000000..dfa9a97 --- /dev/null +++ b/roles/environment/tasks/_prepare_installer.yml @@ -0,0 +1,102 @@ +--- +- name: Speed-up Bootstrap process + when: not (custom_iso | bool) + ansible.builtin.lineinfile: + path: /etc/pacman.conf + regexp: ^#ParallelDownloads = + line: ParallelDownloads = 20 + +- name: Wait for pacman lock to be released + when: not (custom_iso | bool) + ansible.builtin.wait_for: + path: /var/lib/pacman/db.lck + state: absent + timeout: 120 + changed_when: false + +- name: Setup Pacman + when: + - not (custom_iso | bool) + - item.os is not defined or os in item.os + community.general.pacman: + update_cache: true + force: true + name: "{{ item.name }}" + state: latest + loop: + - { name: glibc } + - { name: dnf, os: [almalinux, fedora, rhel, rocky] } + - { 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 + +- name: Prepare /iso mount and repository for RHEL-based systems + when: os == "rhel" + block: + - name: Create /iso directory + ansible.builtin.file: + path: /usr/local/install/redhat/dvd + 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: >- + {{ + _rom_devices[-1] + if _rom_devices | length > 1 + else (_rom_devices[0] | default('/dev/sr1')) + }} + + - name: Mount RHEL ISO + ansible.posix.mount: + src: "{{ environment_rhel_iso_device }}" + path: /usr/local/install/redhat/dvd + fstype: iso9660 + 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: + dest: /etc/rpm/macros + content: "%_pkgverify_level none\n" + mode: "0644" + +- name: Configure RHEL Repos for installation + when: is_rhel | bool + block: + - name: Create directories for repository files and RPM GPG keys + ansible.builtin.file: + path: /etc/yum.repos.d + state: directory + mode: "0755" + + - name: Create RHEL repository file + ansible.builtin.template: + src: "{{ os }}.repo.j2" + dest: /etc/yum.repos.d/{{ os }}.repo + mode: "0644" diff --git a/roles/environment/tasks/_thirdparty.yml b/roles/environment/tasks/_thirdparty.yml new file mode 100644 index 0000000..da3dd37 --- /dev/null +++ b/roles/environment/tasks/_thirdparty.yml @@ -0,0 +1,27 @@ +--- +- name: Check for third-party preparation tasks + run_once: true + become: false + delegate_to: localhost + vars: + ansible_connection: local + block: + - name: Resolve third-party preparation task path + ansible.builtin.set_fact: + environment_thirdparty_tasks_path: >- + {{ + thirdparty_tasks + if thirdparty_tasks | regex_search('^/') + else playbook_dir + '/' + thirdparty_tasks + }} + + - name: Stat third-party preparation tasks + ansible.builtin.stat: + path: "{{ environment_thirdparty_tasks_path }}" + register: environment_thirdparty_tasks_stat + +- name: Run third-party preparation tasks + when: + - thirdparty_tasks | length > 0 + - environment_thirdparty_tasks_stat.stat.exists + ansible.builtin.include_tasks: "{{ environment_thirdparty_tasks_path }}" diff --git a/roles/environment/tasks/main.yml b/roles/environment/tasks/main.yml index 5f7d78f..ce72093 100644 --- a/roles/environment/tasks/main.yml +++ b/roles/environment/tasks/main.yml @@ -2,278 +2,14 @@ - name: Configure work environment become: "{{ hypervisor_type != 'vmware' }}" block: - - name: Wait for connection - ansible.builtin.wait_for_connection: - timeout: 180 - delay: 5 + - name: Detect and validate live environment + ansible.builtin.include_tasks: _detect_live.yml - - name: Gather facts - ansible.builtin.setup: - - - name: Check for live environment markers - ansible.builtin.stat: - path: "{{ item }}" - loop: - - /run/archiso - - /run/live - - /run/initramfs - - /run/initramfs/live - register: environment_live_marker_stat - changed_when: false - - - name: Determine root filesystem type - ansible.builtin.set_fact: - environment_root_fstype: >- - {{ - ansible_mounts - | selectattr('mount', 'equalto', '/') - | map(attribute='fstype') - | list - | first - | default('') - | lower - }} - environment_archiso_present: >- - {{ - ( - environment_live_marker_stat.results - | selectattr('item', 'equalto', '/run/archiso') - | selectattr('stat.exists') - | list - | length - ) > 0 - }} - changed_when: false - - - name: Identify live environment indicators - ansible.builtin.set_fact: - environment_is_live_environment: >- - {{ - ( - environment_live_marker_stat.results - | selectattr('stat.exists') - | list - | length - ) > 0 - or environment_root_fstype in ['overlay', 'overlayfs', 'squashfs', 'aufs'] - or (ansible_hostname | default('') | lower is search('live')) - }} - changed_when: false - - - name: Abort if target is not a live environment - ansible.builtin.assert: - that: - - environment_is_live_environment | bool - fail_msg: | - PRODUCTION SYSTEM DETECTED - ABORTING - - The target system does not appear to be a live installer environment. - This playbook must run from a live ISO to avoid wiping production data. - - Boot from a live installer (Arch, Debian, Ubuntu, etc.) and retry. - quiet: true - - - name: Abort if the host is not booted from the Arch install media - when: - - not (custom_iso | bool) - - not environment_archiso_present | bool - ansible.builtin.fail: - msg: This host is not booted from the Arch install media! - - - name: Select primary Network Interface - when: hypervisor_type == "vmware" - ansible.builtin.set_fact: - environment_interface_name: >- - {{ - ( - (ansible_facts.interfaces | default(ansible_facts['ansible_interfaces'] | default([]))) - | reject('equalto', 'lo') - | list - | first - ) - | default('') - }} - changed_when: false - - - name: Set IP-Address - when: - - hypervisor_type == "vmware" - - system_cfg.network.ip is defined and system_cfg.network.ip | string | length > 0 - ansible.builtin.command: >- - ip addr replace {{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }} - dev {{ environment_interface_name }} - register: environment_ip_result - changed_when: environment_ip_result.rc == 0 - - - name: Set Default Gateway - when: - - hypervisor_type == "vmware" - - system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length > 0 - - system_cfg.network.ip is defined and system_cfg.network.ip | string | length > 0 - ansible.builtin.command: "ip route replace default via {{ system_cfg.network.gateway }}" - register: environment_gateway_result - changed_when: environment_gateway_result.rc == 0 - - - name: Synchronize clock via NTP - ansible.builtin.command: timedatectl set-ntp true - register: environment_ntp_result - changed_when: environment_ntp_result.rc == 0 - - - name: Configure SSH for root login - when: hypervisor_type == "vmware" and hypervisor_cfg.ssh | bool - block: - - name: Allow login - ansible.builtin.replace: - path: /etc/ssh/sshd_config - regexp: "{{ item.regexp }}" - replace: "{{ item.replace }}" - loop: - - regexp: "^#?PermitEmptyPasswords.*" - replace: "PermitEmptyPasswords yes" - - regexp: "^#?PermitRootLogin.*" - replace: "PermitRootLogin yes" - loop_control: - label: "{{ item.replace }}" - - - name: Reload SSH service to apply changes - ansible.builtin.service: - name: sshd - state: reloaded - - - name: Set SSH connection for VMware - ansible.builtin.set_fact: - ansible_connection: ssh - ansible_user: root + - name: Configure network and connectivity + ansible.builtin.include_tasks: _configure_network.yml - name: Prepare installer environment - block: - - name: Speed-up Bootstrap process - when: not (custom_iso | bool) - ansible.builtin.lineinfile: - path: /etc/pacman.conf - regexp: ^#ParallelDownloads = - line: ParallelDownloads = 20 - - - name: Wait for pacman lock to be released - when: not (custom_iso | bool) - ansible.builtin.wait_for: - path: /var/lib/pacman/db.lck - state: absent - timeout: 120 - changed_when: false - - - name: Setup Pacman - when: - - not (custom_iso | bool) - - item.os is not defined or os in item.os - community.general.pacman: - update_cache: true - force: true - name: "{{ item.name }}" - state: latest - loop: - - { name: glibc } - - { name: dnf, os: [almalinux, fedora, rhel, rocky] } - - { 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 - - - name: Prepare /iso mount and repository for RHEL-based systems - when: os == "rhel" - block: - - name: Create /iso directory - ansible.builtin.file: - path: /usr/local/install/redhat/dvd - 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: >- - {{ - _rom_devices[-1] - if _rom_devices | length > 1 - else (_rom_devices[0] | default('/dev/sr1')) - }} - - - name: Mount RHEL ISO - ansible.posix.mount: - src: "{{ environment_rhel_iso_device }}" - path: /usr/local/install/redhat/dvd - fstype: iso9660 - 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: - dest: /etc/rpm/macros - content: "%_pkgverify_level none\n" - mode: "0644" - - - name: Configure RHEL Repos for installation - when: is_rhel | bool - block: - - name: Create directories for repository files and RPM GPG keys - ansible.builtin.file: - path: /etc/yum.repos.d - state: directory - mode: "0755" - - - name: Create RHEL repository file - ansible.builtin.template: - src: "{{ os }}.repo.j2" - dest: /etc/yum.repos.d/{{ os }}.repo - mode: "0644" - - - name: Check for third-party preparation tasks - run_once: true - become: false - delegate_to: localhost - vars: - ansible_connection: local - block: - - name: Resolve third-party preparation task path - ansible.builtin.set_fact: - environment_thirdparty_tasks_path: >- - {{ - thirdparty_tasks - if thirdparty_tasks | regex_search('^/') - else playbook_dir + '/' + thirdparty_tasks - }} - changed_when: false - - - name: Stat third-party preparation tasks - ansible.builtin.stat: - path: "{{ environment_thirdparty_tasks_path }}" - register: environment_thirdparty_tasks_stat - changed_when: false + ansible.builtin.include_tasks: _prepare_installer.yml - name: Run third-party preparation tasks - when: - - thirdparty_tasks | length > 0 - - environment_thirdparty_tasks_stat.stat.exists - ansible.builtin.include_tasks: "{{ environment_thirdparty_tasks_path }}" + ansible.builtin.include_tasks: _thirdparty.yml