From 525edb723191729c2e799f62e7a80e339d2897cd Mon Sep 17 00:00:00 2001 From: Sandwich Date: Tue, 19 Mar 2024 23:02:50 +0100 Subject: [PATCH] Initial commit --- .gitignore | 5 + README.md | 0 inventory_sample.ini | 29 +++ main.yml | 89 +++++++++ roles/bootstrap/tasks/main.yml | 43 +++++ roles/bootstrap/vars/packages.yml | 160 ++++++++++++++++ roles/cis/tasks/main.yml | 175 ++++++++++++++++++ roles/cleanup/tasks/main.yml | 75 ++++++++ roles/configuration/tasks/main.yml | 165 +++++++++++++++++ roles/configuration/templates/custom.sh.j2 | 12 ++ roles/configuration/templates/firstrun.sh.j2 | 145 +++++++++++++++ roles/configuration/templates/network.j2 | 18 ++ .../templates/sudo_lecture.txt.j2 | 11 ++ roles/environment/tasks/main.yml | 72 +++++++ roles/partitioning/tasks/btrfs.yml | 46 +++++ roles/partitioning/tasks/ext4.yml | 23 +++ roles/partitioning/tasks/main.yml | 125 +++++++++++++ roles/partitioning/tasks/xfs.yml | 13 ++ roles/virtualization/tasks/libvirt.yml | 41 ++++ roles/virtualization/tasks/main.yml | 2 + roles/virtualization/tasks/proxmox.yml | 48 +++++ roles/virtualization/tasks/vmware.yml | 33 ++++ .../templates/cloud-network-config.yml.j2 | 11 ++ .../templates/cloud-user-data.yml.j2 | 10 + roles/virtualization/templates/vm.xml.j2 | 55 ++++++ templates/almalinux.repo.j2 | 43 +++++ templates/fedora.repo.j2 | 25 +++ vars_example.yml | 18 ++ 28 files changed, 1492 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 inventory_sample.ini create mode 100644 main.yml create mode 100644 roles/bootstrap/tasks/main.yml create mode 100644 roles/bootstrap/vars/packages.yml create mode 100644 roles/cis/tasks/main.yml create mode 100644 roles/cleanup/tasks/main.yml create mode 100644 roles/configuration/tasks/main.yml create mode 100644 roles/configuration/templates/custom.sh.j2 create mode 100644 roles/configuration/templates/firstrun.sh.j2 create mode 100644 roles/configuration/templates/network.j2 create mode 100644 roles/configuration/templates/sudo_lecture.txt.j2 create mode 100644 roles/environment/tasks/main.yml create mode 100644 roles/partitioning/tasks/btrfs.yml create mode 100644 roles/partitioning/tasks/ext4.yml create mode 100644 roles/partitioning/tasks/main.yml create mode 100644 roles/partitioning/tasks/xfs.yml create mode 100644 roles/virtualization/tasks/libvirt.yml create mode 100644 roles/virtualization/tasks/main.yml create mode 100644 roles/virtualization/tasks/proxmox.yml create mode 100644 roles/virtualization/tasks/vmware.yml create mode 100644 roles/virtualization/templates/cloud-network-config.yml.j2 create mode 100644 roles/virtualization/templates/cloud-user-data.yml.j2 create mode 100644 roles/virtualization/templates/vm.xml.j2 create mode 100644 templates/almalinux.repo.j2 create mode 100644 templates/fedora.repo.j2 create mode 100644 vars_example.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6549529 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +inventory.yml +inventory.yaml + +vars.yml +vars.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/inventory_sample.ini b/inventory_sample.ini new file mode 100644 index 0000000..d147fa2 --- /dev/null +++ b/inventory_sample.ini @@ -0,0 +1,29 @@ +[promox-kvm] +192.168.122.10 +192.168.122.11 + +[promox-kvm:vars] +vm_gw=192.168.122.1 +vm_dns=1.1.1.1 + +[192.168.122.10] +hostname=proxy +vm_id=300 +os=archlinux +filesystem=btrfs +vm_memory=2048 +vm_ballo=1024 +vm_cpus=2 +vm_size=5 +vm_nif=vmbr1 + +[192.168.122.11] +hostname=database +vm_id=101 +os=archlinux +filesystem=btrfs +vm_memory=6144 +vm_ballo=3072 +vm_cpus=4 +vm_size=40 +vm_nif=vmbr1 \ No newline at end of file diff --git a/main.yml b/main.yml new file mode 100644 index 0000000..39afeb0 --- /dev/null +++ b/main.yml @@ -0,0 +1,89 @@ +--- +- name: Create and configure VMs + hosts: all + strategy: free + gather_facts: false + become: true + vars_prompt: + - name: user_name + prompt: | + What is your username? + private: false + + - name: user_password + prompt: | + What is your password? + confirm: true + + - name: root_password + prompt: | + What is your root password? + confirm: true + + - name: hypervisor + prompt: | + Select an Hypervisor: + - libvirt + - proxmox + - vmware + private: false + default: "proxmox" + + - name: install_drive + prompt: | + "Enter the drive to install the system (default: /dev/sda)" + confirm: true + private: false + default: "/dev/sda" + vars_files: vars.yml + pre_tasks: + - name: Set ansible_python_interpreter + when: os | lower in ["almalinux", "rhel9", "rhel8"] + set_fact: + ansible_python_interpreter: /usr/bin/python3 + + - name: Validate variables + assert: + that: + - hypervisor in hypervisor_list + - filesystem in filesystem_list + - os in os_list + fail_msg: "Invalid input specified, please try again" + + - name: Set connection + when: hypervisor == "vmware" + set_fact: + ansible_connection: vmware_tools + + roles: + + - role: virtualization + when: install_type == "virtual" + become: false + vars: + ansible_connection: local + + - role: environment + vars: + ansible_connection: "{{ 'vmware_tools' if hypervisor == 'vmware' else 'ssh' }}" + + - role: partitioning + vars: + boot_partition_suffix: 1 + main_partition_suffix: 2 + + - role: bootstrap + + - role: configuration + + - role: cis + when: cis == true + + - role: cleanup + when: install_type == "virtual" + + tasks: + - name: Reboot system + when: hypervisor != "libvirt" + command: reboot + ignore_errors: true \ No newline at end of file diff --git a/roles/bootstrap/tasks/main.yml b/roles/bootstrap/tasks/main.yml new file mode 100644 index 0000000..076dd96 --- /dev/null +++ b/roles/bootstrap/tasks/main.yml @@ -0,0 +1,43 @@ +--- +- name: Include Packages + include_vars: + file: packages.yml + name: role_packages + +- name: Run OS-specific bootstrap process + block: + - name: Bootstrap ArchLinux + when: os | lower == 'archlinux' + command: pacstrap /mnt {{ role_packages.archlinux | join(' ') }} --asexplicit + + - name: Bootstrap Debian System + when: os | lower in ['debian11', 'debian12'] + shell: "{{ item }}" + with_items: + - debootstrap --include={{ role_packages[os].base | join(',') }} {{ 'bullseye' if os == 'debian11' else 'bookworm' }} /mnt http://deb.debian.org/debian/ + - arch-chroot /mnt apt install -y {{ role_packages[os].extra | join(' ') }} + - arch-chroot /mnt apt remove -y libcups2 libavahi-common3 libavahi-common-data + + - name: Bootstrap AlmaLinux 9 + when: os | lower == 'almalinux' + shell: "{{ item }}" + with_items: + - dnf --releasever=9 --best --repo=alma-baseos --installroot=/mnt --setopt=install_weak_deps=False groupinstall -y base core + - echo "nameserver 1.0.0.1" > /mnt/etc/resolv.conf + - arch-chroot /mnt dnf --releasever=9 --setopt=install_weak_deps=False install -y {{ role_packages.almalinux | join(' ') }} + + - name: Bootstrap Fedora 39 + when: os | lower == 'fedora' + shell: "{{ item }}" + with_items: + - dnf --releasever=39 --best --repo=fedora --repo=fedora-updates --installroot=/mnt --setopt=install_weak_deps=False groupinstall -y critical-path-base core + - arch-chroot /mnt dnf --releasever=39 --setopt=install_weak_deps=False install -y {{ role_packages.fedora | join(' ') }} + - arch-chroot /mnt dnf reinstall -y grub2-efi-x64 kernel + + - name: Bootstrap RHEL System + when: os | lower in ['rhel8', 'rhel9'] + shell: "{{ item }}" + with_items: + - "dnf --releasever={{ '8' if os == 'rhel8' else '9' }} --installroot=/mnt --setopt=install_weak_deps=False groupinstall -y base core" + - "echo 'nameserver 1.0.0.1' > /mnt/etc/resolv.conf" + - "arch-chroot /mnt dnf --releasever={{ '8' if os == 'rhel8' else '9' }} --setopt=install_weak_deps=False install -y {{ role_packages[os] | join(' ') }}" \ No newline at end of file diff --git a/roles/bootstrap/vars/packages.yml b/roles/bootstrap/vars/packages.yml new file mode 100644 index 0000000..ec926d9 --- /dev/null +++ b/roles/bootstrap/vars/packages.yml @@ -0,0 +1,160 @@ +--- +archlinux: + - base + - btrfs-progs + - cronie + - dhcpcd + - efibootmgr + - firewalld + - fish + - grub + - htop + - linux + - logrotate + - lrzsz + - lsof + - lvm2 + - ncdu + - neofetch + - networkmanager + - nfs-utils + - openssh + - open-vm-tools + - prometheus-node-exporter + - python-psycopg2 + - qemu-guest-agent + - reflector + - rsync + - screen + - sudo + - vim + - wireguard-tools + +debian11: + base: + - apparmor-utils + - btrfs-progs + - xfsprogs + - chrony + - cron + - grub-efi + - grub-efi-amd64-signed + - grub2-common + - gnupg + - linux-image-amd64 + - locales + - logrotate + - lvm2 + - net-tools + - openssh-server + - python3 + - sudo + extra: + - curl + - firewalld + - htop + - network-manager + - screen + - open-vm-tools + - python-is-python3 + - ncdu + - neofetch + - lrzsz + - libpam-pwquality + - rsync + - software-properties-common + - syslog-ng + - tcpd + - fish + - vim + - wget + +debian12: + base: + - btrfs-progs + - xfsprogs + - cron + - grub-efi + - grub-efi-amd64-signed + - grub2-common + - gnupg + - linux-image-amd64 + - locales + - lvm2 + + extra: + - apparmor-utils + - chrony + - curl + - firewalld + - fish + - htop + - network-manager + - screen + - open-vm-tools + - python-is-python3 + - ncdu + - neofetch + - logrotate + - lrzsz + - libpam-pwquality + - rsync + - software-properties-common + - sudo + - syslog-ng + - tcpd + - net-tools + - openssh-server + - python3 + - vim + - wget + +fedora: + - dhcp-client + - efibootmgr + - grub2 + - grub2-efi-x64-modules + - lrzsz + - nfs-utils + - open-vm-tools + - shim + - telnet + - vim-default-editor + - zstd + +almalinux: + - dhcp-client + - efibootmgr + - grub2 + - grub2-efi-x64-modules + - lrzsz + - nfs-utils + - open-vm-tools + - shims + - telnet + - vim + - zstd + +rhel8: + - dhcp-client + - efibootmgr + - grub2 + - grub2-efi-x64-modules + - lrzsz + - nfs-utils + - open-vm-tools + - shim + - telnet + - zstd + +rhel9: + - dhcp-client + - efibootmgr + - grub2 + - grub2-efi-x64-modules + - lrzsz + - nfs-utils + - open-vm-tools + - shim + - telnet + - zstd \ No newline at end of file diff --git a/roles/cis/tasks/main.yml b/roles/cis/tasks/main.yml new file mode 100644 index 0000000..0acf254 --- /dev/null +++ b/roles/cis/tasks/main.yml @@ -0,0 +1,175 @@ +- name: Configurationg System for CIS conformity + block: + - name: Disable Kernel Modules + copy: + dest: /mnt/etc/modprobe.d/cis.conf + content: | + CIS LVL 3 Restrictions + install freevxfs /bin/true + install jffs2 /bin/true + install hfs /bin/true + install hfsplus /bin/true + install squashfs /bin/true + install udf /bin/true + install usb-storage /bin/true + + install dccp /bin/true + install sctp /bin/true + install rds /bin/true + install tipc /bin/true + + - name: Create USB Rules + copy: + dest: /mnt/etc/udev/rules.d/10-cis_usb_devices.sh + content: | + By default, disable all. + ACTION=="add", SUBSYSTEMS=="usb", TEST=="authorized_default", ATTR{authorized_default}="0" + + Enable hub devices. + ACTION=="add", ATTR{bDeviceClass}=="09", TEST=="authorized", ATTR{authorized}="1" + + Enables keyboard devices + ACTION=="add", ATTR{product}=="*[Kk]eyboard*", TEST=="authorized", ATTR{authorized}="1" + + PS2-USB converter + ACTION=="add", ATTR{product}=="*Thinnet TM*", TEST=="authorized", ATTR{authorized}="1" + + - name: Create a consolidated sysctl configuration file + copy: + dest: /mnt/etc/sysctl.d/10-cis.conf + content: | + ## CIS Sysctl configurations + net.ipv4.conf.all.log_martians = 1 + net.ipv4.conf.all.rp_filter = 1 + net.ipv4.conf.all.secure_redirects = 0 + net.ipv4.conf.all.send_redirects = 0 + net.ipv4.conf.all.accept_redirects = 0 + net.ipv4.conf.default.log_martians = 1 + net.ipv4.conf.default.rp_filter = 1 + net.ipv4.conf.default.secure_redirects = 0 + net.ipv4.conf.default.send_redirects = 0 + net.ipv4.conf.default.accept_redirects = 0 + net.ipv6.conf.all.accept_redirects = 0 + net.ipv6.conf.all.disable_ipv6 = 1 + net.ipv6.conf.default.accept_redirects = 0 + net.ipv6.conf.default.disable_ipv6 = 1 + net.ipv6.conf.lo.disable_ipv6 = 1 + + # - name: Adjust login.defs + # replace: + # path: /mnt/etc/login.defs + # regexp: "{{ item.regexp }}" + # replace: "{{ item.replace }}" + # loop: + # - { regexp: '^PASS_MAX_DAYS.*', replace: 'PASS_MAX_DAYS 90' } + # - { regexp: '^PASS_MIN_DAYS.*', replace: 'PASS_MIN_DAYS 7' } + # - { regexp: '^UMASK.*', replace: 'UMASK 027' } + + - name: Create allow files + file: + path: "{{ item }}" + state: touch + mode: '0600' + loop: + - /mnt/etc/at.allow + - /mnt/etc/cron.allow + + - name: Add Security related lines into config files + lineinfile: + path: "{{ item.path }}" + line: "{{ item.content }}" + loop: + - { path: '/mnt/etc/security/limits.conf', content: '* hard core 0' } + - { path: '/mnt/etc/security/pwquality.conf', content: 'minlen = 14' } + - { path: '/mnt/etc/security/pwquality.conf', content: 'dcredit = -1' } + - { path: '/mnt/etc/security/pwquality.conf', content: 'ucredit = -1' } + - { path: '/mnt/etc/security/pwquality.conf', content: 'ocredit = -1' } + - { path: '/mnt/etc/security/pwquality.conf', content: 'lcredit = -1' } + - { path: '/mnt/etc/bash.bashrc', content: 'umask 077' } + - { path: '/mnt/etc/bash.bashrc', content: 'export TMOUT=3000' } + - { path: '/mnt/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: '/mnt/etc/pam.d/common-auth', content: 'auth required pam_faillock.so onerr=fail audit silent deny=5 unlock_time=900' } + - { path: '/mnt/etc/pam.d/common-account', content: 'account required pam_faillock.so' } + - { path: '/mnt/etc/pam.d/common-password', content: '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' } + + - name: Set permissions for various files and directories + file: + path: "{{ item.path }}" + owner: "{{ item.owner | default(omit) }}" + group: "{{ item.group | default(omit) }}" + mode: "{{ item.mode }}" + loop: + - { 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: '754' } + - { path: '/mnt/usr/lib/dbus-1.0/dbus-daemon-launch-helper', mode: '754' } + - { path: '/mnt/usr/libexec/polkit-agent-helper-1', mode: '755' } + - { path: '/mnt/usr/bin/{{ "fusermount" if os == "debian11" else "fusermount3" }}', mode: '755' } + - { path: '/mnt/usr/bin/{{ "write.ul" if os == "debian11" else "write" }}', mode: '755' } + - { path: '/mnt/usr/lib/x86_64-linux-gnu/utempter/utempter', mode: '755' } + - { path: '/mnt/home/svcansible', mode: '750' } + + - name: Adjust SSHD config + lineinfile: + path: /mnt/etc/ssh/sshd_config + regexp: '^\s*#?{{ item.option }}\s+.*$' + line: '{{ item.option }} {{ item.value }}' + with_items: + - {option: 'LogLevel', value: 'VERBOSE'} + - {option: 'LoginGraceTime', value: '60'} + - {option: 'PermitRootLogin', value: 'no'} + - {option: 'StrictModes', value: 'yes'} + - {option: 'MaxAuthTries', value: '4'} + - {option: 'MaxSessions', value: '10'} + - {option: 'MaxStartups', value: '10:30:60'} + - {option: 'PubkeyAuthentication', value: 'yes'} + - {option: 'HostbasedAuthentication', value: 'no'} + - {option: 'IgnoreRhosts', value: 'yes'} + - {option: 'PasswordAuthentication', value: 'no'} + - {option: 'PermitEmptyPasswords', value: 'no'} + - {option: 'KerberosAuthentication', value: 'no'} + - {option: 'GSSAPIAuthentication', value: 'no'} + - {option: 'GSSAPIKeyExchange', value: 'no'} + - {option: 'AllowAgentForwarding', value: 'no'} + - {option: 'AllowTcpForwarding', value: 'no'} + - {option: 'ChallengeResponseAuthentication', value: 'no'} + - {option: 'GatewayPorts', value: 'no'} + - {option: 'X11Forwarding', value: 'no'} + - {option: 'PermitUserEnvironment', value: 'no'} + - {option: 'ClientAliveInterval', value: '300'} + - {option: 'ClientAliveCountMax', value: '0'} + - {option: 'PermitTunnel', value: 'no'} + - {option: 'Banner', value: '/etc/issue.net'} + + - name: Append CIS Specific configurations to sshd_config + lineinfile: + path: /mnt/etc/ssh/sshd_config + line: | + + ## CIS Specific + Protocol 2 + + ### Ciphers and keying ### + RekeyLimit 512M 6h + KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256 + Ciphers chacha20-poly1305@openssh.com,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,hmac-sha2-512,hmac-sha2-256 + ########################### + + AllowStreamLocalForwarding no + PermitUserRC no + + AllowUsers svcansible + AllowGroups * + DenyUsers nobody + DenyGroups nobody \ No newline at end of file diff --git a/roles/cleanup/tasks/main.yml b/roles/cleanup/tasks/main.yml new file mode 100644 index 0000000..056f0c1 --- /dev/null +++ b/roles/cleanup/tasks/main.yml @@ -0,0 +1,75 @@ +- name: Setup Cleanup + when: hypervisor == "proxmox" + delegate_to: localhost + become: false + block: + - name: Cleanup Setup Disks + community.general.proxmox_disk: + api_host: "{{ hypervisor_url }}" + api_user: "{{ hypervisor_username }}" + api_password: "{{ hypervisor_password }}" + name: "{{ hostname }}" + vmid: "{{ vm_id }}" + disk: "{{ item }}" + state: absent + loop: + - ide0 + - ide1 + +- name: Remove CD-ROM from VM in vCenter + when: hypervisor == "vmware" + delegate_to: localhost + ignore_errors: true + vmware_guest: + hostname: "{{ hypervisor_url }}" + username: "{{ hypervisor_username }}" + password: "{{ hypervisor_password }}" + validate_certs: no + datacenter: "{{ hypervisor_cluster }}" + name: "{{ hostname }}" + cdrom: + - controller_number: 0 + unit_number: 0 + controller_type: "sata" + type: iso + iso_path: "{{ boot_iso }}" + state: absent + +- name: Remove Archiso and cloud-init disks + when: hypervisor == "libvirt" + delegate_to: localhost + become: false + block: + - name: Stop the VM + community.libvirt.virt: + name: "{{ hostname }}" + state: shutdown + + - name: Remove cloud-init disk + file: + path: "{{ storage_path | default('/var/lib/libvirt/images/') }}{{ hostname }}-cloudinit.iso" + state: absent + + - name: Get list of CD-ROM devices + shell: virsh --connect qemu:///system domblklist {{ hostname }} --details | grep 'cdrom' | awk '{print $3}' + changed_when: false + register: cdrom_devices + + - name: Wait for VM to spin down + wait_for: + timeout: 15 + + - name: Remove CD-ROM devices + when: cdrom_devices.stdout_lines | length > 0 + command: virsh --connect qemu:///system detach-disk {{ hostname }} {{ item }} --persistent + with_items: "{{ cdrom_devices.stdout_lines }}" + + - name: Start the VM + community.libvirt.virt: + name: "{{ hostname }}" + state: running + + - name: Wait for VM to boot up + delegate_to: "{{ inventory_hostname }}" + wait_for_connection: + timeout: 300 \ No newline at end of file diff --git a/roles/configuration/tasks/main.yml b/roles/configuration/tasks/main.yml new file mode 100644 index 0000000..9e92cfd --- /dev/null +++ b/roles/configuration/tasks/main.yml @@ -0,0 +1,165 @@ +- name: Configuration + block: + - name: Generate fstab + shell: genfstab -LU /mnt > /mnt/etc/fstab + + - name: Append TempFS to fstab + lineinfile: + path: /mnt/etc/fstab + line: "{{ item }}" + insertafter: EOF + with_items: + - "" + - "# TempFS" + - "tmpfs /tmp tmpfs defaults,nosuid,nodev,noexec 0 0" + - "tmpfs /var/tmp tmpfs defaults,nosuid,nodev,noexec 0 0" + - "tmpfs /dev/shm tmpfs defaults,noexec 0 0" + + - name: Set local timezone + command: '{{ item }}' + with_items: + - systemctl daemon-reload + - arch-chroot /mnt ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime + + - name: Generate adjtime file + command: arch-chroot /mnt /usr/sbin/hwclock --systohc + + - name: Setup locales + block: + - name: Configure locale.gen + lineinfile: + dest: /mnt/etc/locale.gen + regexp: '{{ item.regex }}' + line: '{{ item.line }}' + loop: + - {regex: en_US\.UTF-8 UTF-8, line: en_US.UTF-8 UTF-8} + + - name: Generate locales + command: arch-chroot /mnt /usr/sbin/locale-gen + + - name: Set hostname + copy: + content: "{{ hostname }}" + dest: /mnt/etc/hostname + + - name: Add host entry to /etc/hosts + lineinfile: + path: /mnt/etc/hosts + line: "{{ ansible_host }} {{ hostname }}" + state: present + + - name: Create vconsole.conf + copy: + content: "KEYMAP=de-latin1-nodeadkeys" + dest: /mnt/etc/vconsole.conf + + - name: Create locale.conf + copy: + content: "LANG=en_US.UTF-8" + dest: /mnt/etc/locale.conf + + - name: SSH permit Password + replace: + path: /mnt/etc/ssh/sshd_config + regexp: '#PasswordAuthentication yes' + replace: 'PasswordAuthentication yes' + + - name: Enable Systemd Services + block: + - name: Enable sshd + when: os | lower == "archlinux" + command: arch-chroot /mnt systemctl enable sshd NetworkManager logrotate + + - name: Configure grub + when: os | lower != "fedora" and os | lower != "almalinux" and os | lower != "rhel8" and os | lower != "rhel9" + block: + - name: Add commandline information to grub config + lineinfile: + dest: /mnt/etc/default/grub + regexp: ^GRUB_CMDLINE_LINUX_DEFAULT= + line: 'GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3"' + + - name: Change Grub time + lineinfile: + dest: /mnt/etc/default/grub + regexp: ^GRUB_TIMEOUT= + line: 'GRUB_TIMEOUT=0' + + - name: Configure Bootloader + block: + - name: Install Bootloader + command: arch-chroot /mnt {% if os | lower != "archlinux" and os | lower != "debian11" and os | lower != "debian12" %}/usr/sbin/efibootmgr -c -L '{{ os }}' -d "{{ install_drive }}" -wwp 1 -l '\efi\EFI\{{ os }}\shimx64.efi'{% else %}/usr/sbin/grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id={{ os }}{% endif %} + + - name: Generate grub config + command: arch-chroot /mnt {% if os | lower != "archlinux" and os | lower != "debian11" and os | lower != "debian12" %}/usr/sbin/grub2-mkconfig -o /boot/efi/EFI/{{ os }}/grub.cfg{% else %}/usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg{% endif %} + + - name: Extra Configuration + when: os | lower != "archlinux" + block: + - name: Append lines to vimrc + lineinfile: + path: "{{ '/mnt/etc/vim/vimrc' if os|lower == 'debian11' or os|lower == 'debian12' else '/mnt/etc/vimrc' }}" + line: "{{ item }}" + insertafter: EOF + with_items: + - "set encoding=utf-8" + - "set number" + - "set autoindent" + - "set smartindent" + - "set mouse=a" + + - name: Copy FirstRun Script + template: + src: firstrun.sh.j2 + dest: /mnt/root/firstrun.sh + mode: '0755' + + - name: Copy Custom Shell config + template: + src: custom.sh.j2 + dest: /mnt/etc/profile.d/custom.sh + + - name: Setup Network + block: + - name: Generate UUID for Network Profile + command: "uuidgen" + register: net_uuid + + - name: Retrieve Network Interface Name + shell: "ip r | awk 'NR==1 {print $5}'" + register: net_inf + + - name: Copy NetworkManager keyfile + template: + src: network.j2 + dest: /mnt/etc/NetworkManager/system-connections/LAN.nmconnection + mode: '0600' + + - name: Setup user account + block: + - name: Create user account + command: '{{ item }}' + with_items: + - arch-chroot /mnt /usr/sbin/useradd --create-home --user-group --groups {{ "sudo" if os|lower == "debian11" or os|lower == "debian12" else "wheel" }} {{ user_name }} --password {{ user_password | password_hash('sha512') }} --shell /bin/bash + - arch-chroot /mnt /usr/sbin/usermod --password '{{ root_password | password_hash('sha512') }}' root --shell /bin/bash + + - name: Add SSH public key to authorized_keys + when: user_public_key is defined + lineinfile: + path: "/mnt/home/{{ user_name }}/.ssh/authorized_keys" + line: "{{ user_public_key }}" + owner: 1000 + group: 1000 + mode: "0600" + create: yes + + - name: Give sudo access to wheel group + copy: + content: "{{ '%sudo ALL=(ALL) ALL' if os|lower == 'debian11' or os|lower == 'debian12' else '%wheel ALL=(ALL) ALL' }}" + dest: /mnt/etc/sudoers.d/01-wheel + mode: 0440 + validate: /usr/sbin/visudo --check --file=%s + + - name: Fix SELinux + when: (os | lower == "almalinux" or os | lower == "fedora" or os | lower == "rhel8" or os | lower == "rhel9") + command: touch /mnt/.autorelabel \ No newline at end of file diff --git a/roles/configuration/templates/custom.sh.j2 b/roles/configuration/templates/custom.sh.j2 new file mode 100644 index 0000000..dc87f73 --- /dev/null +++ b/roles/configuration/templates/custom.sh.j2 @@ -0,0 +1,12 @@ +# Prompt Style +if [ "$PS1" ]; then + PS1="\[\033[01;32m\][\u@\h:\l \[\033[01;34m\]\W\[\033[00m\]]\\$ " +fi + +# Timestamps and instant apply +HISTTIMEFORMAT="%F %T " +PROMPT_COMMAND="history -a;$PROMPT_COMMAND" + +# History Size +HISTFILESIZE= +HISTSIZE= \ No newline at end of file diff --git a/roles/configuration/templates/firstrun.sh.j2 b/roles/configuration/templates/firstrun.sh.j2 new file mode 100644 index 0000000..996b30b --- /dev/null +++ b/roles/configuration/templates/firstrun.sh.j2 @@ -0,0 +1,145 @@ +#!/bin/bash + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[1;34m' +NC='\033[0m' # No Color + +# Ask for and set the hostname +echo -e "${BLUE}Enter the hostname:${NC}" +read -r new_hostname + +# Detect the network interface +network_interface=$(nmcli -t -f DEVICE connection show --active | head -n 1) + +# Ask for and set the IP address +echo -e "${BLUE}Enter the IP address (eg.: 10.11.x.x/24):${NC}" +read -r ip_address + +# Ask for and set the DNS server +default_dns1="10.11.23.10" +default_dns2="10.11.23.18" +echo -e "${BLUE}Enter the DNS server (default: $default_dns1, $default_dns2):${NC}" +read -r dns_server +dns_server=${dns_server:-"$default_dns1 $default_dns2"} + +# Ask if Btrfs compression should be enabled +if [[ $(df -T / | awk 'NR==2 {print $2}') == "btrfs" ]]; then + echo -e "${BLUE}Do you want to enable Btrfs compression? (y/n):${NC}" + read -r enable_compression +fi + +if [[ "$enable_compression" == "y" || "$enable_compression" == "Y" ]]; then + # Ask for the use case + echo -e "${BLUE} the use case:${NC}" + echo "1. Databases, File Storage, etc (recommended compression level: 15)" + echo "2. Real-time compression (recommended compression level: 3)" + echo "3. Custom compression level" + read -r use_case + + # Set the recommended compression level based on the use case + case "$use_case" in + 1) compression_level=15 ;; + 2) compression_level=3 ;; + 3) echo -e "${BLUE}Enter the custom compression level (1-15):${NC}" + read -r compression_level ;; + *) echo -e "${RED}Invalid use case. Exiting script.${NC}"; exit 1 ;; + esac +fi + +# Ask if CheckMK Agent should be installed +echo -e "${BLUE}Do you want to install the CheckMK Agent? (y/n):${NC}" +read -r install_checkmk_agent + +# Ask if ports and services should be opened +echo -e "${BLUE}Do you want to open any ports or services? (y/n):${NC}" +read -r open_ports_services + +if [[ "$open_ports_services" == "y" || "$open_ports_services" == "Y" ]]; then + # Ask for and set the services to open + echo -e "${BLUE}Enter the services to open (comma-separated):${NC}" + read -r services + + # Ask for and set the ports to open + echo -e "${BLUE}Enter the ports to open (comma-separated):${NC}" + read -r ports +fi + +# Apply Changes +echo -e "${BLUE}Are you sure you want to apply the changes? This may cause a loss of SSH connection. (y/n):${NC}" +read -r answer + +# Check the user's response +if [[ "$answer" == "y" || "$answer" == "Y" ]]; then + # Comment out the script execution line in .bashrc + sed -i '/~\/firstrun\.sh/s/^/#/' ~/.bashrc + hostnamectl set-hostname "$new_hostname" + + nmcli device modify "$network_interface" ipv4.dns "$dns_server" > /dev/null + nmcli device modify "$network_interface" ipv6.method ignore > /dev/null + nmcli device modify "$network_interface" ipv4.addresses "$ip_address" ipv4.method manual > /dev/null + + # Modify /etc/hosts file + ip_address=$(echo "$ip_address" | sed 's/.\{3\}$//') + if grep "$ip_address" /etc/hosts > /dev/null 2>&1; then + echo "IP address already exists in /etc/hosts" + else + # Add IP address and hostname after the "127.0.0.1 localhost" entry + sed -i '1a\'"$ip_address\t$new_hostname" /etc/hosts + + if [ $? -eq 0 ]; then + echo "IP address and hostname added to /etc/hosts" + else + echo "Failed to add IP address and hostname to /etc/hosts" + fi + fi + + # Modify Btrfs compression settings in /etc/fstab + if [[ "$enable_compression" == "y" || "$enable_compression" == "Y" ]]; then + if ! grep -q "compress=zstd" /etc/fstab; then + sed -i "/btrfs/s/defaults/defaults,compress=zstd:$compression_level/" /etc/fstab + else + sed -i "/btrfs/s/compress=zstd:[0-9]*/compress=zstd:$compression_level/" /etc/fstab + fi + else + if grep -q "compress=zstd" /etc/fstab; then + sed -i "/btrfs/s/,compress=zstd:[0-9]*//" /etc/fstab + fi + fi + + if [[ "$install_checkmk_agent" == "y" || "$install_checkmk_agent" == "Y" ]]; then + # Run the CheckMK Agent installation script + bash Scripts/install_checkmk_agent.sh + fi + + if [[ "$open_ports_services" == "y" || "$open_ports_services" == "Y" ]]; then + # Open the specified services + IFS=',' read -ra service_array <<< "$services" + for service in "${service_array[@]}"; do + firewall-cmd --add-service="$service" --permanent > /dev/null + done + + # Open the specified ports + IFS=',' read -ra port_array <<< "$ports" + for port in "${port_array[@]}"; do + firewall-cmd --add-port="$port"/tcp --permanent > /dev/null + done + + firewall-cmd --reload > /dev/null 2>&1 + fi + + # Open port 6556/tcp for CheckMK Agent if it was installed + if [[ "$install_checkmk_agent" == "y" || "$install_checkmk_agent" == "Y" ]]; then + firewall-cmd --add-port=6556/tcp --permanent > /dev/null 2>&1 + firewall-cmd --reload > /dev/null 2>&1 + else + firewall-cmd --remove-port=6556/tcp --permanent > /dev/null 2>&1 + firewall-cmd --reload > /dev/null 2>&1 + fi + + echo -e "${GREEN}Changes applied successfully.${NC}" +else + echo -e "${RED}Changes not applied. Exiting script.${NC}" + exit 0 +fi \ No newline at end of file diff --git a/roles/configuration/templates/network.j2 b/roles/configuration/templates/network.j2 new file mode 100644 index 0000000..f87ae10 --- /dev/null +++ b/roles/configuration/templates/network.j2 @@ -0,0 +1,18 @@ +[connection] +id=LAN +uuid={{ net_uuid.stdout }} +type=ethernet +interface-name={{ net_inf.stdout }} + +[ethernet] + +[ipv4] +address={{ vm_ip }},{{ vm_gw }} +dns={{ vm_dns }} +method=manual + +[ipv6] +addr-gen-mode=default +method=disabled + +[proxy] \ No newline at end of file diff --git a/roles/configuration/templates/sudo_lecture.txt.j2 b/roles/configuration/templates/sudo_lecture.txt.j2 new file mode 100644 index 0000000..80dbf77 --- /dev/null +++ b/roles/configuration/templates/sudo_lecture.txt.j2 @@ -0,0 +1,11 @@ + + \^V// + |. .| I AM (G)ROOT! + - \ - / _ + \_| |_/ + \ \ + __/_/__ + |_______| With great power comes great responsibility. + \ / Use sudo wisely. + \___/ + diff --git a/roles/environment/tasks/main.yml b/roles/environment/tasks/main.yml new file mode 100644 index 0000000..89fdca0 --- /dev/null +++ b/roles/environment/tasks/main.yml @@ -0,0 +1,72 @@ +- name: Configre work environment + become: true + block: + - name: Wait for connection + wait_for_connection: + timeout: 300 + delay: 5 + + - name: Gather facts + setup: + + - name: Check if host is booted from the Arch install media + stat: + path: /run/archiso + register: archiso_stat + + - name: Abort if the host is not booted from the Arch install media + fail: + msg: "This host is not booted from the Arch install media!" + when: not archiso_stat.stat.exists + + - name: Setect Interface + when: hypervisor == "vmware" + shell: "ip l | awk -F': ' '!/lo/{print $2; exit}'" + register: interface_name + + - name: Set IP-Address + when: hypervisor == "vmware" + command: ip addr replace {{ ansible_host }}/24 dev {{ interface_name.stdout }} + + - name: Set Default Gateway + when: hypervisor == "vmware" + command: ip route replace default via {{ vm_gw }} + + - name: Synchronize clock via NTP + command: timedatectl set-ntp true + + - name: Speed-up Bootstrap process + lineinfile: + path: /etc/pacman.conf + regexp: '^#ParallelDownloads =' + line: 'ParallelDownloads = 20' + + - name: Wait for Pacman + wait_for: + timeout: 15 + + - name: Setup Pacman + pacman: + update_cache: yes + name: + - glibc + - debootstrap + - debian-archive-keyring + - dnf + state: latest + retries: 3 + delay: 15 + ignore_errors: no + + - name: Configure RHEL Repos for installation + when: os | lower == "almalinux" or os | lower == "fedora" + block: + - name: Create directories for repository files and RPM GPG keys + file: + path: /etc/yum.repos.d + state: directory + + - name: Create RHEL repository file + template: + src: '{{ os | lower }}.repo.j2' + dest: '/etc/yum.repos.d/{{ os | lower }}.repo' \ No newline at end of file diff --git a/roles/partitioning/tasks/btrfs.yml b/roles/partitioning/tasks/btrfs.yml new file mode 100644 index 0000000..0c38b52 --- /dev/null +++ b/roles/partitioning/tasks/btrfs.yml @@ -0,0 +1,46 @@ +--- +- name: Setup BTRFS + block: + - name: Create btrfs filesystem in main volume + filesystem: + dev: '{{ install_drive }}{{ main_partition_suffix }}' + fstype: btrfs + force: yes + + - name: Prepare BTRFS Subvolume + mount: + path: /mnt + src: '{{ install_drive }}{{ main_partition_suffix }}' + fstype: btrfs + opts: rw,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async + state: mounted + + - name: Enable quotas on Btrfs filesystem + command: btrfs quota enable /mnt + + - name: Make root subvolumes + when: cis == true or item.subvol not in ['var_log', 'var_log_audit'] + command: btrfs su cr /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }} + loop: + - { subvol: root } + - { subvol: home } + - { subvol: var } + - { subvol: var_log } + - { subvol: var_log_audit } + + - name: Set quotas for subvolumes + when: cis == true or item.subvol not in ['var_log', 'var_log_audit'] + command: btrfs qgroup limit {{ item.quota }} /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }} + loop: + - { subvol: root, quota: '12G' } + - { subvol: home, quota: '2G' } + - { subvol: var, quota: '2G' } + - { subvol: var_log, quota: '2G' } + - { subvol: var_log_audit, quota: '1536M' } + + - name: Unmount Partition + mount: + path: /mnt + src: '{{ install_drive }}{{ main_partition_suffix }}' + fstype: btrfs + state: unmounted \ No newline at end of file diff --git a/roles/partitioning/tasks/ext4.yml b/roles/partitioning/tasks/ext4.yml new file mode 100644 index 0000000..ba88ddf --- /dev/null +++ b/roles/partitioning/tasks/ext4.yml @@ -0,0 +1,23 @@ +--- +- name: Create and format ext4 logical volumes + when: cis == true or item.lv not in ['var_log', 'var_log_audit'] + filesystem: + dev: '/dev/sys/{{ item.lv }}' + fstype: ext4 + force: yes + loop: + - { lv: root } + - { lv: home } + - { lv: var } + - { lv: var_log } + - { lv: var_log_audit } + +- name: Remove Unsupported features for older Systems + when: (os | lower == 'debian11') and (cis == true or item.lv not in ['var_log', 'var_log_audit']) + command: tune2fs -O "^orphan_file,^metadata_csum_seed" "/dev/sys/{{ item.lv }}" + loop: + - { lv: root } + - { lv: home } + - { lv: var } + - { lv: var_log } + - { lv: var_log_audit } \ No newline at end of file diff --git a/roles/partitioning/tasks/main.yml b/roles/partitioning/tasks/main.yml new file mode 100644 index 0000000..4bb4c2f --- /dev/null +++ b/roles/partitioning/tasks/main.yml @@ -0,0 +1,125 @@ +--- +- name: Partition install drive + block: + - name: Prepare partitions + ignore_errors: true + command: "{{ item.cmd }}" + loop: + - { cmd: "umount -l /mnt" } + - { cmd: "vgremove -f sys" } + - { cmd: "find /dev -wholename \"{{ install_drive }}*\" -exec wipefs --force --all {} \\;" } + loop_control: + label: "{{ item.cmd }}" + + - name: Define partitions + parted: + device: "{{ install_drive }}" + label: gpt + number: "{{ item.number }}" + part_end: "{{ item.part_end | default(omit) }}" + part_start: "{{ item.part_start | default(omit) }}" + name: "{{ item.name }}" + flags: "{{ item.flags | default(omit) }}" + state: present + loop: + - { number: 1, part_end: '500MiB', name: 'boot', flags: ['boot', 'esp'] } + - { number: 2, part_start: '500MiB', name: 'root' } + +- name: Create LVM logical volumes + when: filesystem != 'btrfs' + block: + - name: Create LVM volume group + lvg: + vg: sys + pvs: '{{ install_drive }}{{ main_partition_suffix }}' + + - name: Create LVM logical volumes + when: cis or (not cis and item.lv != 'var_log' and item.lv != 'var_log_audit') + lvol: + vg: sys + lv: "{{ item.lv }}" + size: "{{ item.size }}" + state: present + loop: + - { lv: 'root', size: '12G' } + - { lv: 'home', size: '2G' } + - { lv: 'var', size: '2G' } + - { lv: 'var_log', size: '2G' } + - { lv: 'var_log_audit', size: '1.5G' } + +- name: Create filesystems + block: + - name: Create FAT32 filesystem in boot partition + filesystem: + dev: '{{ install_drive }}{{ boot_partition_suffix }}' + fstype: vfat + opts: -F32 + force: yes + + - name: Create filesystem + include_tasks: "{{ filesystem }}.yml" + + - name: Get UUID for boot filesystem + command: blkid -s UUID -o value '{{ install_drive }}{{ boot_partition_suffix }}' + changed_when: false + register: boot_uuid + + - name: Get UUID for main filesystem + command: blkid -s UUID -o value '{{ install_drive }}{{ main_partition_suffix }}' + changed_when: false + register: main_uuid + + - name: Get UUIDs for LVM filesystems + when: filesystem != 'btrfs' and (cis == true or item not in ['var_log', 'var_log_audit']) + command: blkid -s UUID -o value /dev/sys/{{ item }} + changed_when: false + register: uuid_result + loop: + - root + - home + - var + - var_log + - var_log_audit + + - set_fact: + uuid_root: "{{ uuid_result.results[0].stdout_lines }}" + uuid_home: "{{ uuid_result.results[1].stdout_lines }}" + uuid_var: "{{ uuid_result.results[2].stdout_lines }}" + uuid_var_log: "{{ uuid_result.results[3].stdout_lines if cis == true else '' }}" + uuid_var_log_audit: "{{ uuid_result.results[4].stdout_lines if cis == true else '' }}" + when: filesystem != 'btrfs' + +- name: Mount filesystems + block: + - name: Mount filesystems and subvolumes + when: "cis or (not cis and item.path != '/var/log' and item.path != '/var/log/audit')" + mount: + path: "/mnt{{ item.path }}" + src: "{{ 'UUID=' + (main_uuid.stdout if filesystem == 'btrfs' else item.uuid) }}" + fstype: "{{ filesystem }}" + opts: "{{ item.opts }}" + state: mounted + loop: + - { path: '', uuid: "{{ uuid_root[0] | default(omit) }}", opts: "{{ 'defaults' if filesystem != 'btrfs' else 'rw,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async,subvol=@' }}" } + - { path: '/home', uuid: "{{ uuid_home[0] | default(omit) }}", opts: "{{ 'defaults,nosuid,nodev' if filesystem != 'btrfs' else 'rw,nosuid,nodev,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async,subvol=@home' }}" } + - { path: '/var', uuid: "{{ uuid_var[0] | default(omit) }}", opts: "{{ 'defaults,nosuid,nodev' if filesystem != 'btrfs' else 'rw,nosuid,nodev,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async,subvol=@var' }}" } + - { path: '/var/log', uuid: "{{ uuid_var_log[0] | default(omit) }}", opts: "{{ 'defaults,nosuid,nodev,noexec' if filesystem != 'btrfs' else 'rw,nosuid,nodev,noexec,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async,subvol=@var_log' }}" } + - { path: '/var/log/audit', uuid: "{{ uuid_var_log_audit[0] | default(omit) }}", opts: "{{ 'defaults,nosuid,nodev,noexec' if filesystem != 'btrfs' else 'rw,nosuid,nodev,noexec,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async,subvol=@var_log_audit' }}" } + + - name: Mount tmp and var_tmp filesystems + mount: + path: "/mnt{{ item.path }}" + src: tmpfs + fstype: tmpfs + opts: defaults,nosuid,nodev,noexec + state: mounted + loop: + - { path: '/tmp' } + - { path: '/var/tmp' } + + - name: Mount boot filesystem + mount: + path: /mnt/boot + src: UUID={{ boot_uuid.stdout }} + fstype: vfat + state: mounted \ No newline at end of file diff --git a/roles/partitioning/tasks/xfs.yml b/roles/partitioning/tasks/xfs.yml new file mode 100644 index 0000000..bdd5b1f --- /dev/null +++ b/roles/partitioning/tasks/xfs.yml @@ -0,0 +1,13 @@ +--- +- name: Create and format XFS logical volumes + when: cis == true or item.lv not in ['var_log', 'var_log_audit'] + filesystem: + dev: '/dev/sys/{{ item.lv }}' + fstype: xfs + force: yes + loop: + - { lv: root } + - { lv: home } + - { lv: var } + - { lv: var_log } + - { lv: var_log_audit } \ No newline at end of file diff --git a/roles/virtualization/tasks/libvirt.yml b/roles/virtualization/tasks/libvirt.yml new file mode 100644 index 0000000..5556a2f --- /dev/null +++ b/roles/virtualization/tasks/libvirt.yml @@ -0,0 +1,41 @@ +- name: Check if VM disk exists + delegate_to: localhost + stat: + path: "{{ storage_path | default('/var/lib/libvirt/images/') }}{{ hostname }}.qcow2" + register: vm_disk_stat + +- name: Create VM disk + when: not vm_disk_stat.stat.exists + delegate_to: localhost + command: "qemu-img create -f qcow2 {{ storage_path | default('/var/lib/libvirt/images/') }}{{ hostname }}.qcow2 {{ vm_size }}G" + +- name: Generate Random MAC Address + delegate_to: localhost + shell: openssl rand -hex 5 | sed 's/\(..\)/\1:/g; s/.$//' | sed 's/^/02:/' + changed_when: false + register: mac_address_output + +- name: Render cloud config templates + delegate_to: localhost + template: + src: "{{ item.src }}" + dest: "/tmp/{{ item.dest_prefix }}-{{ hostname }}.yml" + loop: + - { src: "cloud-user-data.yml.j2", dest_prefix: "cloud-user-data" } + - { src: "cloud-network-config.yml.j2", dest_prefix: "cloud-network-config" } + +- name: Create cloud-init disk + delegate_to: localhost + command: "cloud-localds {{ storage_path | default('/var/lib/libvirt/images/') }}{{ hostname }}-cloudinit.iso /tmp/cloud-user-data-{{ hostname }}.yml -N /tmp/cloud-network-config-{{ hostname }}.yml" + +- name: Create VM using libvirt + delegate_to: localhost + community.libvirt.virt: + command: define + xml: "{{ lookup('template', 'vm.xml.j2') }}" + +- name: start vm + delegate_to: localhost + community.libvirt.virt: + name: "{{ hostname }}" + state: running \ No newline at end of file diff --git a/roles/virtualization/tasks/main.yml b/roles/virtualization/tasks/main.yml new file mode 100644 index 0000000..45eb882 --- /dev/null +++ b/roles/virtualization/tasks/main.yml @@ -0,0 +1,2 @@ +- name: Create Virtual Machine + include_tasks: "{{ hypervisor }}.yml" \ No newline at end of file diff --git a/roles/virtualization/tasks/proxmox.yml b/roles/virtualization/tasks/proxmox.yml new file mode 100644 index 0000000..56bd2b7 --- /dev/null +++ b/roles/virtualization/tasks/proxmox.yml @@ -0,0 +1,48 @@ +- name: Deploy VM on Proxmox + delegate_to: localhost + proxmox_kvm: + api_host: "{{ hypervisor_url }}" + api_user: "{{ hypervisor_username }}" + api_password: "{{ hypervisor_password }}" + ciuser: "{{ user_name }}" + cipassword: "{{ user_password }}" + ciupgrade: 0 + node: "{{ hypervisor_node }}" # Proxmox node name + vmid: "{{ vm_id }}" # Unique ID for the VM + name: "{{ hostname }}" # Name of the VM + cpu: "host" + cores: "{{ vm_cpus }}" # Number of CPU cores + memory: "{{ vm_memory }}" # Memory size in MB + numa_enabled: true + hotplug: "network,disk" + bios: ovmf + boot: "ac" + scsihw: "virtio-scsi-single" + scsi: + scsi0: "{{ hypervisor_storage }}:{{ vm_size }}" # Disk configuration + efidisk0: + efitype: "4m" + format: "raw" + pre_enrolled_keys: false + storage: "{{ hypervisor_storage }}" + ide: + ide0: "{{ boot_iso }},media=cdrom" + ide1: "{{ hypervisor_storage }}:cloudinit" + net: + net0: "virtio,bridge={{ vm_nif }}{% if vlan_name is defined and vlan_name %},tag={{ vlan_name }}{% endif %}" + ipconfig: + ipconfig0: "ip={{ vm_ip }},gw={{ vm_gw }}" + nameservers: "{{ vm_dns }}" + onboot: true # Start the VM on boot + state: present # Ensure the VM is present + +- name: Start VM on Proxmox + delegate_to: localhost + proxmox_kvm: + api_host: "{{ hypervisor_url }}" + api_user: "{{ hypervisor_username }}" + api_password: "{{ hypervisor_password }}" + node: "{{ hypervisor_node }}" + name: "{{ hostname }}" + vmid: "{{ vm_id }}" + state: started # Ensure the VM is present \ No newline at end of file diff --git a/roles/virtualization/tasks/vmware.yml b/roles/virtualization/tasks/vmware.yml new file mode 100644 index 0000000..eb62372 --- /dev/null +++ b/roles/virtualization/tasks/vmware.yml @@ -0,0 +1,33 @@ +- name: Create VM in vCenter + delegate_to: localhost + vmware_guest: + hostname: "{{ hypervisor_url }}" + username: "{{ hypervisor_username }}" + password: "{{ hypervisor_password }}" + validate_certs: no + datacenter: "{{ hypervisor_cluster }}" + cluster: "{{ hypervisor_node }}" + folder: "{{ vm_path }}" + name: "{{ hostname }}" + guest_id: "otherGuest64" + state: poweredon + disk: + - size_gb: "{{ vm_size }}" + type: thin + datastore: "{{ hypervisor_storage }}" + hardware: + memory_mb: "{{ vm_memory }}" + num_cpus: "{{ vm_cpus }}" + boot_firmware: "efi" + secure_boot: false + cdrom: + - controller_number: 0 + unit_number: 0 + controller_type: "sata" + state: present + type: iso + iso_path: "{{ boot_iso }}" + networks: + - vlan: "{{ vlan_name }}" + type: dhcp + ignore_errors: yes \ No newline at end of file diff --git a/roles/virtualization/templates/cloud-network-config.yml.j2 b/roles/virtualization/templates/cloud-network-config.yml.j2 new file mode 100644 index 0000000..ca3e391 --- /dev/null +++ b/roles/virtualization/templates/cloud-network-config.yml.j2 @@ -0,0 +1,11 @@ +network: + version: 2 + ethernets: + id0: + match: + macaddress: "{{ mac_address_output.stdout }}" + addresses: + - "{{ vm_ip }}" + gateway4: "{{ vm_gw }}" + nameservers: + addresses: ['1.1.1.1', '1.0.0.1'] \ No newline at end of file diff --git a/roles/virtualization/templates/cloud-user-data.yml.j2 b/roles/virtualization/templates/cloud-user-data.yml.j2 new file mode 100644 index 0000000..51d230d --- /dev/null +++ b/roles/virtualization/templates/cloud-user-data.yml.j2 @@ -0,0 +1,10 @@ +#cloud-config +hostname: "archiso" +ssh_pwauth: true +users: + - name: "{{ user_name }}" + primary_group: "{{ user_name }}" + groups: users + sudo: ALL=(ALL) NOPASSWD:ALL + passwd: "{{ user_password | password_hash('sha512') }}" + lock_passwd: False \ No newline at end of file diff --git a/roles/virtualization/templates/vm.xml.j2 b/roles/virtualization/templates/vm.xml.j2 new file mode 100644 index 0000000..f4762cd --- /dev/null +++ b/roles/virtualization/templates/vm.xml.j2 @@ -0,0 +1,55 @@ + + {{ hostname }} + {{ vm_memory | int * 1024 }} + {{ vm_ballo | int * 1024 }} + {{ vm_cpus }} + + hvm + + + + /usr/share/edk2/x64/OVMF_CODE.secboot.fd + + + + + + + + + + destroy + restart + destroy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/almalinux.repo.j2 b/templates/almalinux.repo.j2 new file mode 100644 index 0000000..dc24881 --- /dev/null +++ b/templates/almalinux.repo.j2 @@ -0,0 +1,43 @@ +[alma-appstream] +name=AlmaLinux $releasever - AppStream +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/appstream +# baseurl=https://repo.almalinux.org/almalinux/$releasever/AppStream/$basearch/os/ +enabled=1 +gpgcheck=1 +countme=1 +gpgkey=https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-$releasever +metadata_expire=86400 +enabled_metadata=1 + +[alma-baseos] +name=AlmaLinux $releasever - BaseOS +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/baseos +# baseurl=https://repo.almalinux.org/almalinux/$releasever/BaseOS/$basearch/os/ +enabled=1 +gpgcheck=1 +countme=1 +gpgkey=https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-$releasever +metadata_expire=86400 +enabled_metadata=1 + +[alma-extras] +name=AlmaLinux $releasever - Extras +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/extras +# baseurl=https://repo.almalinux.org/almalinux/$releasever/extras/$basearch/os/ +enabled=1 +gpgcheck=1 +countme=1 +gpgkey=https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-$releasever +metadata_expire=86400 +enabled_metadata=0 + +[alma-highavailability] +name=AlmaLinux $releasever - HighAvailability +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/highavailability +# baseurl=https://repo.almalinux.org/almalinux/$releasever/HighAvailability/$basearch/os/ +enabled=1 +gpgcheck=1 +countme=1 +gpgkey=https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-$releasever +metadata_expire=86400 +enabled_metadata=0 \ No newline at end of file diff --git a/templates/fedora.repo.j2 b/templates/fedora.repo.j2 new file mode 100644 index 0000000..8ae1d65 --- /dev/null +++ b/templates/fedora.repo.j2 @@ -0,0 +1,25 @@ +[fedora] +name=Fedora $releasever - $basearch +#baseurl=http://download.example/pub/fedora/linux/releases/$releasever/Everything/$basearch/os/ +metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-$releasever&arch=$basearch +enabled=1 +countme=1 +metadata_expire=86400 +repo_gpgcheck=0 +type=rpm +gpgcheck=1 +gpgkey=https://getfedora.org/static/fedora.gpg +skip_if_unavailable=False + +[fedora-updates] +name=Fedora $releasever - $basearch - Updates +#baseurl=http://download.example/pub/fedora/linux/updates/$releasever/Everything/$basearch/ +metalink=https://mirrors.fedoraproject.org/metalink?repo=updates-released-f$releasever&arch=$basearch +enabled=1 +countme=1 +repo_gpgcheck=0 +type=rpm +gpgcheck=1 +metadata_expire=86400 +gpgkey=https://getfedora.org/static/fedora.gpg +skip_if_unavailable=False \ No newline at end of file diff --git a/vars_example.yml b/vars_example.yml new file mode 100644 index 0000000..dafe85a --- /dev/null +++ b/vars_example.yml @@ -0,0 +1,18 @@ +ansible_user: "{{ user_name }}" +ansible_password: "{{ user_password }}" +ansible_become_password: "{{ user_password }}" +ansible_ssh_extra_args: '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' + +vm_ip: "{{ inventory_hostname }}/24" +hypervisor_list: ["libvirt", "proxmox", "vmware", "none"] +filesystem_list: ["btrfs", "ext4", "xfs"] +os_list: ["archlinux", "almalinux", "debian11", "debian12", "fedora"] +install_type: "virtual" +cis: false + +hypervisor_url: "192.168.0.2" +hypervisor_username: "root@pam" +hypervisor_password: "SomePassword" +hypervisor_node: "NodeName" +hypervisor_storage: "local-btrfs" +boot_iso: "local-btrfs:iso/archlinux-x86_64.iso" \ No newline at end of file