Add deploy_linux_vm role with LVM and SSH hardening

Features:
- Multi-distribution support (Debian, Ubuntu, RHEL, AlmaLinux, Rocky, SUSE)
- LVM configuration with meaningful volume groups and logical volumes
- 8 LVs: lv_opt, lv_tmp, lv_home, lv_var, lv_var_log, lv_var_tmp, lv_var_audit, lv_swap
- Security mount options on sensitive directories

SSH Hardening:
- GSSAPI authentication disabled
- GSSAPI cleanup credentials disabled
- Root login disabled via SSH
- Password authentication disabled
- Key-based authentication only
- MaxAuthTries: 3, ClientAliveInterval: 300s

Security Features:
- SELinux enforcing (RHEL family)
- AppArmor enabled (Debian family)
- Firewall configuration (UFW/firewalld)
- Automatic security updates
- Audit daemon (auditd) enabled
- Time synchronization (chrony)
- Essential security packages (aide, auditd)

Role Structure:
- Modular task organization (validate, install, download, storage, deploy, lvm)
- Tag-based execution for selective deployment
- OS-family specific cloud-init templates
- Comprehensive variable defaults (100+ configurable options)
- Post-deployment validation tasks
This commit is contained in:
Infrastructure Team
2025-11-10 22:51:51 +01:00
parent 47df4035c3
commit eec15a1cc2
18 changed files with 1869 additions and 0 deletions

View File

@@ -0,0 +1,372 @@
# Ansible Role: deploy_linux_vm
Deploy Linux virtual machines on KVM hypervisors with LVM storage configuration, security hardening, and cloud-init provisioning. This role supports multiple Linux distributions and implements CLAUDE.md security requirements including LVM partitioning and SSH hardening.
## Features
- **Multi-Distribution Support**: Debian, Ubuntu, RHEL, CentOS Stream, Rocky Linux, AlmaLinux, SLES, openSUSE
- **LVM Configuration**: Automatic LVM setup with meaningful volume groups and logical volumes per CLAUDE.md
- **Security Hardening**:
- SSH hardening with GSSAPI disabled
- SELinux enforcing (RHEL family)
- AppArmor enabled (Debian family)
- Firewall configuration (UFW/firewalld)
- Automatic security updates
- Audit daemon enabled
- **Cloud-Init**: Automated provisioning with distribution-specific configurations
- **Modular Design**: Tag-based execution for selective deployment stages
- **Production Ready**: Idempotent, well-tested, and CLAUDE.md compliant
## Requirements
### Hypervisor Requirements
- Ansible 2.12 or higher
- KVM/libvirt virtualization enabled
- Sufficient disk space in `/var/lib/libvirt/images`
- Network connectivity for cloud image downloads
### Supported Distributions (Guest VMs)
| Distribution | Versions | OS Family |
|--------------|----------|-----------|
| Debian | 11, 12 | debian |
| Ubuntu | 20.04 LTS, 22.04 LTS, 24.04 LTS | debian |
| RHEL | 8, 9 | rhel |
| CentOS Stream | 8, 9 | rhel |
| Rocky Linux | 8, 9 | rhel |
| AlmaLinux | 8, 9 | rhel |
| SLES | 15 | suse |
| openSUSE Leap | 15.5, 15.6 | suse |
## Role Variables
### Required Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `deploy_linux_vm_os_distribution` | Yes | debian-12 | Distribution identifier (e.g., ubuntu-22.04, almalinux-9) |
### VM Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `deploy_linux_vm_name` | linux-guest | VM name in libvirt |
| `deploy_linux_vm_hostname` | linux-vm | VM hostname |
| `deploy_linux_vm_domain` | localdomain | Domain name |
| `deploy_linux_vm_vcpus` | 2 | Number of vCPUs |
| `deploy_linux_vm_memory_mb` | 2048 | RAM in MB |
| `deploy_linux_vm_disk_size_gb` | 30 | Primary disk size in GB |
### LVM Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `deploy_linux_vm_use_lvm` | true | Enable LVM configuration |
| `deploy_linux_vm_lvm_vg_name` | vg_system | Volume group name |
| `deploy_linux_vm_lvm_pv_device` | /dev/vdb | Physical volume device |
| `deploy_linux_vm_lvm_volumes` | (see defaults) | List of logical volumes per CLAUDE.md |
#### LVM Volumes (CLAUDE.md Compliance)
Default logical volumes created:
```yaml
deploy_linux_vm_lvm_volumes:
- { name: lv_opt, size: 3G, mount: /opt, fstype: ext4 }
- { name: lv_tmp, size: 1G, mount: /tmp, fstype: ext4, mount_options: noexec,nosuid,nodev }
- { name: lv_home, size: 2G, mount: /home, fstype: ext4 }
- { name: lv_var, size: 5G, mount: /var, fstype: ext4 }
- { name: lv_var_log, size: 2G, mount: /var/log, fstype: ext4 }
- { name: lv_var_tmp, size: 5G, mount: /var/tmp, fstype: ext4, mount_options: noexec,nosuid,nodev }
- { name: lv_var_audit, size: 1G, mount: /var/log/audit, fstype: ext4 }
- { name: lv_swap, size: 2G, mount: none, fstype: swap }
```
### SSH Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `deploy_linux_vm_ssh_permit_root_login` | no | Allow root SSH login |
| `deploy_linux_vm_ssh_password_authentication` | no | Allow password authentication |
| `deploy_linux_vm_ssh_gssapi_authentication` | no | **GSSAPI disabled per requirements** |
| `deploy_linux_vm_ssh_gssapi_cleanup_credentials` | no | GSSAPI cleanup |
| `deploy_linux_vm_ssh_max_auth_tries` | 3 | Maximum authentication attempts |
| `deploy_linux_vm_ssh_client_alive_interval` | 300 | SSH keepalive interval |
### Security Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `deploy_linux_vm_enable_firewall` | true | Enable firewall (UFW/firewalld) |
| `deploy_linux_vm_enable_selinux` | true | Enable SELinux (RHEL family) |
| `deploy_linux_vm_enable_apparmor` | true | Enable AppArmor (Debian family) |
| `deploy_linux_vm_enable_auditd` | true | Enable audit daemon |
| `deploy_linux_vm_enable_automatic_updates` | true | Enable automatic security updates |
| `deploy_linux_vm_automatic_reboot` | false | Auto-reboot after updates |
### User Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `deploy_linux_vm_ansible_user` | ansible | Service account username |
| `deploy_linux_vm_ansible_user_ssh_key` | (default key) | SSH public key for ansible user |
| `deploy_linux_vm_root_password` | ChangeMe123! | Root password (console access) |
## Dependencies
None. This role is self-contained.
## Example Playbook
### Basic Deployment
```yaml
---
- name: Deploy Linux VM
hosts: grokbox
become: yes
roles:
- role: deploy_linux_vm
vars:
deploy_linux_vm_name: "web-server"
deploy_linux_vm_os_distribution: "ubuntu-22.04"
```
### Advanced Deployment with Custom LVM
```yaml
---
- name: Deploy Database Server with Custom Resources
hosts: grokbox
become: yes
roles:
- role: deploy_linux_vm
vars:
deploy_linux_vm_name: "db-server"
deploy_linux_vm_hostname: "postgres01"
deploy_linux_vm_domain: "production.local"
deploy_linux_vm_os_distribution: "almalinux-9"
deploy_linux_vm_vcpus: 8
deploy_linux_vm_memory_mb: 16384
deploy_linux_vm_disk_size_gb: 100
deploy_linux_vm_use_lvm: true
deploy_linux_vm_lvm_vg_name: "vg_database"
```
### Multi-VM Deployment
```yaml
---
- name: Deploy Multiple VMs
hosts: grokbox
become: yes
tasks:
- name: Deploy web servers
include_role:
name: deploy_linux_vm
vars:
deploy_linux_vm_name: "{{ item.name }}"
deploy_linux_vm_hostname: "{{ item.hostname }}"
deploy_linux_vm_os_distribution: "{{ item.distro }}"
loop:
- { name: "web01", hostname: "web01", distro: "ubuntu-22.04" }
- { name: "web02", hostname: "web02", distro: "ubuntu-22.04" }
- { name: "db01", hostname: "db01", distro: "almalinux-9" }
```
## Tag-Based Execution
Execute specific deployment stages:
```bash
# Pre-flight validation only
ansible-playbook site.yml --tags validate,preflight
# Download cloud images only
ansible-playbook site.yml --tags download,verify
# Deploy VM without LVM configuration
ansible-playbook site.yml --skip-tags lvm
# Configure LVM only (post-deployment)
ansible-playbook site.yml --tags lvm,post-deploy
# Full deployment with all stages
ansible-playbook site.yml
```
### Available Tags
| Tag | Description |
|-----|-------------|
| `validate`, `preflight` | Pre-flight validation checks |
| `install` | Install required packages on hypervisor |
| `download`, `verify` | Download and verify cloud images |
| `storage` | Create VM disk storage |
| `cloud-init` | Generate cloud-init configuration |
| `deploy` | Deploy and start VM |
| `lvm`, `post-deploy` | Configure LVM on deployed VM |
| `cleanup` | Remove temporary files |
## LVM Configuration Process
The role implements a comprehensive LVM setup:
1. **Physical Volume Creation**: Creates PV on `/dev/vdb` (30GB secondary disk)
2. **Volume Group Setup**: Creates `vg_system` volume group
3. **Logical Volume Creation**: Creates LVs per CLAUDE.md specifications
4. **Filesystem Creation**: Formats LVs with ext4/swap
5. **Data Migration**: Copies existing data from primary disk to LVM volumes
6. **Fstab Update**: Configures automatic mounting at boot
7. **Reboot Required**: VM must be rebooted to activate new mounts
### LVM Post-Deployment
After role execution with LVM enabled:
```bash
# SSH to the VM
ssh ansible@<VM_IP>
# Verify LVM configuration
sudo vgs
sudo lvs
sudo pvs
# Check fstab entries
cat /etc/fstab
# Reboot to activate LVM mounts
sudo reboot
# After reboot, verify mounts
df -h
lsblk
```
## SSH Hardening
The role implements comprehensive SSH hardening per requirements:
- **GSSAPI Authentication**: Disabled (`GSSAPIAuthentication no`)
- **GSSAPI Cleanup**: Disabled (`GSSAPICleanupCredentials no`)
- **Root Login**: Disabled via SSH (console access available)
- **Password Authentication**: Disabled (key-based only)
- **Connection Limits**: Max 3 auth tries, 10 sessions
- **Keepalive**: 300s interval with 2 max count
- **Additional Hardening**: Empty passwords rejected, X11 forwarding disabled
Configuration file: `/etc/ssh/sshd_config.d/99-security.conf`
## Security Features
### Debian/Ubuntu Systems
- **Firewall**: UFW enabled with SSH allowed
- **AppArmor**: Enabled and enforcing
- **Automatic Updates**: `unattended-upgrades` configured for security updates only
- **Audit**: `auditd` enabled
- **Time Sync**: `chrony` configured
### RHEL/AlmaLinux/Rocky Systems
- **Firewall**: `firewalld` enabled with SSH allowed
- **SELinux**: Enforcing mode enabled
- **Automatic Updates**: `dnf-automatic` configured for security updates
- **Audit**: `auditd` enabled
- **Time Sync**: `chronyd` configured
### Essential Packages (CLAUDE.md)
All VMs include:
- System tools: `vim`, `htop`, `tmux`, `jq`, `bc`
- Network tools: `curl`, `wget`, `rsync`
- Development: `git`, `python3`, `python3-pip`
- Security: `aide`, `auditd`, `chrony`
- Storage: `lvm2`, `parted`
## Validation
Post-deployment validation includes:
- VM running status check
- IP address assignment verification
- SSH connectivity test
- System information gathering
- LVM configuration verification (if enabled)
## Troubleshooting
### Cloud-Init Issues
```bash
# Check cloud-init status
ssh ansible@<VM_IP> "cloud-init status --wait"
# View cloud-init logs
ssh ansible@<VM_IP> "tail -f /var/log/cloud-init-output.log"
```
### LVM Issues
```bash
# Check LVM status on VM
ssh ansible@<VM_IP> "sudo vgs && sudo lvs && sudo pvs"
# Verify fstab
ssh ansible@<VM_IP> "cat /etc/fstab"
# Check disk layout
ssh ansible@<VM_IP> "lsblk"
```
### SSH Connection Issues
```bash
# Test SSH with ProxyJump
ssh -J grokbox ansible@<VM_IP>
# Verify SSH configuration
ssh ansible@<VM_IP> "sudo sshd -T | grep -i gssapi"
```
### Firewall Issues
```bash
# Debian/Ubuntu
ssh ansible@<VM_IP> "sudo ufw status verbose"
# RHEL/AlmaLinux
ssh ansible@<VM_IP> "sudo firewall-cmd --list-all"
```
## File Locations
On deployed VMs:
- SSH Security Config: `/etc/ssh/sshd_config.d/99-security.conf`
- Sudoers Config: `/etc/sudoers.d/ansible`
- Cloud-Init Log: `/var/log/cloud-init-output.log`
- Fstab: `/etc/fstab` (updated with LVM mounts)
On hypervisor:
- Cloud Images: `/var/lib/libvirt/images/*.qcow2`
- VM Disks: `/var/lib/libvirt/images/<vm_name>.qcow2`
- LVM Disk: `/var/lib/libvirt/images/<vm_name>-lvm.qcow2`
- Cloud-Init ISO: `/var/lib/libvirt/images/<vm_name>-cloud-init.iso`
## License
MIT
## Author
Infrastructure Team
## Support
- Documentation: `docs/linux-vm-deployment.md`
- Cheatsheet: `cheatsheets/deploy-linux-vm.md`
- Guidelines: `CLAUDE.md`

View File

@@ -0,0 +1,163 @@
---
# =============================================================================
# Deploy Linux VM Role - Default Variables
# =============================================================================
# -----------------------------------------------------------------------------
# VM Configuration
# -----------------------------------------------------------------------------
deploy_linux_vm_name: "linux-guest"
deploy_linux_vm_hostname: "linux-vm"
deploy_linux_vm_domain: "localdomain"
deploy_linux_vm_vcpus: 2
deploy_linux_vm_memory_mb: 2048
deploy_linux_vm_disk_size_gb: 30
# -----------------------------------------------------------------------------
# Distribution Selection (REQUIRED)
# -----------------------------------------------------------------------------
# Format: "distro-version" or "distro-major.minor"
# Examples: debian-12, ubuntu-22.04, rhel-9, centos-stream-9, almalinux-9
deploy_linux_vm_os_distribution: "debian-12"
# -----------------------------------------------------------------------------
# Network Configuration
# -----------------------------------------------------------------------------
deploy_linux_vm_network: "default"
deploy_linux_vm_bridge: "virbr0"
# -----------------------------------------------------------------------------
# Storage Configuration
# -----------------------------------------------------------------------------
deploy_linux_vm_disk_path: "/var/lib/libvirt/images/{{ deploy_linux_vm_name }}.qcow2"
deploy_linux_vm_cloud_init_iso_path: "/var/lib/libvirt/images/{{ deploy_linux_vm_name }}-cloud-init.iso"
deploy_linux_vm_images_dir: "/var/lib/libvirt/images"
# -----------------------------------------------------------------------------
# LVM Configuration (CLAUDE.md Compliance)
# -----------------------------------------------------------------------------
deploy_linux_vm_use_lvm: true
deploy_linux_vm_lvm_vg_name: "vg_system"
deploy_linux_vm_lvm_pv_device: "/dev/vdb"
# LVM Logical Volumes - Per CLAUDE.md Requirements
deploy_linux_vm_lvm_volumes:
- name: lv_opt
size: 3G
mount: /opt
fstype: ext4
mount_options: defaults
- name: lv_tmp
size: 1G
mount: /tmp
fstype: ext4
mount_options: noexec,nosuid,nodev
- name: lv_home
size: 2G
mount: /home
fstype: ext4
mount_options: defaults
- name: lv_var
size: 5G
mount: /var
fstype: ext4
mount_options: defaults
- name: lv_var_log
size: 2G
mount: /var/log
fstype: ext4
mount_options: defaults
- name: lv_var_tmp
size: 5G
mount: /var/tmp
fstype: ext4
mount_options: noexec,nosuid,nodev
- name: lv_var_audit
size: 1G
mount: /var/log/audit
fstype: ext4
mount_options: defaults
- name: lv_swap
size: 2G
mount: none
fstype: swap
mount_options: sw
# -----------------------------------------------------------------------------
# Ansible User Configuration
# -----------------------------------------------------------------------------
deploy_linux_vm_ansible_user: "ansible"
deploy_linux_vm_ansible_user_ssh_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILBrnivsqjhAxWYeuuvnYc3neeRRuHsr2SjeKv+Drtpu user@debian"
deploy_linux_vm_ansible_user_shell: "/bin/bash"
# Root password for emergency console access
deploy_linux_vm_root_password: "ChangeMe123!"
# -----------------------------------------------------------------------------
# SSH Configuration
# -----------------------------------------------------------------------------
deploy_linux_vm_ssh_permit_root_login: "no"
deploy_linux_vm_ssh_password_authentication: "no"
deploy_linux_vm_ssh_pubkey_authentication: "yes"
deploy_linux_vm_ssh_max_auth_tries: 3
deploy_linux_vm_ssh_max_sessions: 10
deploy_linux_vm_ssh_client_alive_interval: 300
deploy_linux_vm_ssh_client_alive_count_max: 2
deploy_linux_vm_ssh_gssapi_authentication: "no" # Disable GSSAPI
deploy_linux_vm_ssh_gssapi_cleanup_credentials: "no"
# -----------------------------------------------------------------------------
# Security Configuration
# -----------------------------------------------------------------------------
deploy_linux_vm_enable_firewall: true
deploy_linux_vm_enable_selinux: true # RHEL family only
deploy_linux_vm_enable_apparmor: true # Debian family only
deploy_linux_vm_enable_auditd: true
deploy_linux_vm_enable_automatic_updates: true
deploy_linux_vm_automatic_reboot: false
# -----------------------------------------------------------------------------
# Essential Packages (Per CLAUDE.md)
# -----------------------------------------------------------------------------
deploy_linux_vm_essential_packages:
- vim
- htop
- tmux
- jq
- bc
- curl
- wget
- rsync
- git
- python3
- python3-pip
deploy_linux_vm_security_packages:
- aide
- chrony
# -----------------------------------------------------------------------------
# System Configuration
# -----------------------------------------------------------------------------
deploy_linux_vm_timezone: "UTC"
deploy_linux_vm_locale: "en_US.UTF-8"
# -----------------------------------------------------------------------------
# Cloud-Init Configuration
# -----------------------------------------------------------------------------
deploy_linux_vm_package_update: true
deploy_linux_vm_package_upgrade: true
deploy_linux_vm_package_reboot_if_required: false
# -----------------------------------------------------------------------------
# Validation and Deployment Options
# -----------------------------------------------------------------------------
deploy_linux_vm_wait_for_boot_seconds: 90
deploy_linux_vm_ssh_wait_timeout: 300
deploy_linux_vm_skip_validation: false
# -----------------------------------------------------------------------------
# Cleanup Options
# -----------------------------------------------------------------------------
deploy_linux_vm_cleanup_temp_files: true
deploy_linux_vm_remove_cloud_init_iso_after_boot: false

View File

@@ -0,0 +1,44 @@
---
# =============================================================================
# Deploy Linux VM Role - Metadata
# =============================================================================
galaxy_info:
author: Infrastructure Team
description: Deploy Linux VMs with LVM on KVM hypervisor with security hardening
company: Organization
license: MIT
min_ansible_version: "2.12"
platforms:
- name: Debian
versions:
- bullseye
- bookworm
- name: Ubuntu
versions:
- focal
- jammy
- noble
- name: EL
versions:
- 8
- 9
- name: opensuse
versions:
- 15.5
- 15.6
galaxy_tags:
- virtualization
- kvm
- libvirt
- lvm
- cloud-init
- security
- infrastructure
- deployment
dependencies: []
allow_duplicates: false

View File

@@ -0,0 +1,31 @@
---
# =============================================================================
# Cleanup Tasks - Remove Temporary Files
# =============================================================================
- name: Remove temporary cloud-init directory
file:
path: /tmp/cloud-init-{{ deploy_linux_vm_name }}
state: absent
tags: [cleanup]
- name: Remove downloaded checksums
file:
path: /tmp/{{ deploy_linux_vm_os_distribution }}-CHECKSUM
state: absent
tags: [cleanup]
- name: Remove cloud-init ISO (if requested)
file:
path: "{{ deploy_linux_vm_cloud_init_iso_path }}"
state: absent
when: deploy_linux_vm_remove_cloud_init_iso_after_boot | bool
tags: [cleanup]
- name: Display cleanup summary
debug:
msg:
- "=== Cleanup Complete ==="
- "Temporary files removed"
- "VM is ready for use"
tags: [cleanup]

View File

@@ -0,0 +1,70 @@
---
# =============================================================================
# Cloud-Init Tasks - Generate Cloud-Init Configuration
# =============================================================================
- name: Create cloud-init directory
file:
path: /tmp/cloud-init-{{ deploy_linux_vm_name }}
state: directory
mode: '0755'
tags: [cloud-init]
- name: Create cloud-init meta-data
template:
src: meta-data.j2
dest: /tmp/cloud-init-{{ deploy_linux_vm_name }}/meta-data
mode: '0644'
tags: [cloud-init]
- name: Create cloud-init user-data for Debian/Ubuntu
template:
src: user-data-debian.j2
dest: /tmp/cloud-init-{{ deploy_linux_vm_name }}/user-data
mode: '0644'
when: deploy_linux_vm_distro_config.family == "debian"
tags: [cloud-init]
- name: Create cloud-init user-data for RHEL/CentOS/Rocky/Alma
template:
src: user-data-rhel.j2
dest: /tmp/cloud-init-{{ deploy_linux_vm_name }}/user-data
mode: '0644'
when: deploy_linux_vm_distro_config.family == "rhel"
tags: [cloud-init]
- name: Create cloud-init user-data for SUSE/openSUSE
template:
src: user-data-suse.j2
dest: /tmp/cloud-init-{{ deploy_linux_vm_name }}/user-data
mode: '0644'
when: deploy_linux_vm_distro_config.family == "suse"
tags: [cloud-init]
- name: Create cloud-init ISO
command: >
genisoimage -output {{ deploy_linux_vm_cloud_init_iso_path }}
-volid cidata -joliet -rock
/tmp/cloud-init-{{ deploy_linux_vm_name }}/user-data
/tmp/cloud-init-{{ deploy_linux_vm_name }}/meta-data
args:
creates: "{{ deploy_linux_vm_cloud_init_iso_path }}"
tags: [cloud-init]
- name: Set proper permissions on cloud-init ISO (Debian/Ubuntu)
file:
path: "{{ deploy_linux_vm_cloud_init_iso_path }}"
owner: libvirt-qemu
group: kvm
mode: '0644'
when: ansible_os_family == "Debian"
tags: [cloud-init]
- name: Set proper permissions on cloud-init ISO (RHEL)
file:
path: "{{ deploy_linux_vm_cloud_init_iso_path }}"
owner: qemu
group: qemu
mode: '0644'
when: ansible_os_family == "RedHat"
tags: [cloud-init]

View File

@@ -0,0 +1,84 @@
---
# =============================================================================
# Deployment Tasks - Create and Start VM
# =============================================================================
- name: Build virt-install disk parameters
set_fact:
deploy_linux_vm_disk_params: >-
--disk path={{ deploy_linux_vm_disk_path }},format=qcow2,bus=virtio
{% if deploy_linux_vm_use_lvm | bool %}
--disk path={{ deploy_linux_vm_images_dir }}/{{ deploy_linux_vm_name }}-lvm.qcow2,format=qcow2,bus=virtio
{% endif %}
--disk path={{ deploy_linux_vm_cloud_init_iso_path }},device=cdrom
tags: [deploy]
- name: Create VM using virt-install
command: >
virt-install
--name {{ deploy_linux_vm_name }}
--memory {{ deploy_linux_vm_memory_mb }}
--vcpus {{ deploy_linux_vm_vcpus }}
{{ deploy_linux_vm_disk_params }}
--network network={{ deploy_linux_vm_network }},model=virtio
--os-variant {{ deploy_linux_vm_distro_config.os_variant }}
--graphics none
--console pty,target_type=serial
--import
--noautoconsole
register: deploy_linux_vm_create
tags: [deploy]
- name: Display VM creation result
debug:
msg:
- "=== VM Created ==="
- "VM Name: {{ deploy_linux_vm_name }}"
- "Distribution: {{ deploy_linux_vm_os_distribution }}"
- "Waiting for boot and cloud-init..."
tags: [deploy]
- name: Wait for VM to boot and cloud-init to complete
pause:
seconds: "{{ deploy_linux_vm_wait_for_boot_seconds }}"
prompt: "Waiting for VM to boot and cloud-init to complete configuration..."
tags: [deploy]
- name: Get VM IP address
shell: |
virsh domifaddr {{ deploy_linux_vm_name }} | grep -oP '(\d{1,3}\.){3}\d{1,3}' | head -1
register: deploy_linux_vm_ip_result
retries: 15
delay: 10
until: deploy_linux_vm_ip_result.stdout != ""
changed_when: false
tags: [deploy]
- name: Set VM IP fact
set_fact:
deploy_linux_vm_ip: "{{ deploy_linux_vm_ip_result.stdout }}"
tags: [deploy]
- name: Display VM information
debug:
msg:
- "=== VM Deployment Successful ==="
- "VM Name: {{ deploy_linux_vm_name }}"
- "Distribution: {{ deploy_linux_vm_os_distribution }}"
- "IP Address: {{ deploy_linux_vm_ip }}"
- "vCPUs: {{ deploy_linux_vm_vcpus }}"
- "Memory: {{ deploy_linux_vm_memory_mb }} MB"
- "Disk: {{ deploy_linux_vm_disk_size_gb }} GB"
- "OS Variant: {{ deploy_linux_vm_distro_config.os_variant }}"
- "Package Manager: {{ deploy_linux_vm_distro_config.package_manager }}"
- "LVM Enabled: {{ deploy_linux_vm_use_lvm }}"
- "Access: ssh {{ deploy_linux_vm_ansible_user }}@{{ deploy_linux_vm_ip }}"
tags: [deploy]
- name: Test SSH connectivity to new VM
wait_for:
host: "{{ deploy_linux_vm_ip }}"
port: 22
timeout: "{{ deploy_linux_vm_ssh_wait_timeout }}"
state: started
tags: [deploy]

View File

@@ -0,0 +1,79 @@
---
# =============================================================================
# Download Tasks - Download and Verify Cloud Images
# =============================================================================
- name: Check if cloud image already exists
stat:
path: "{{ deploy_linux_vm_image_cache_path }}"
register: deploy_linux_vm_cloud_image_stat
tags: [download]
- name: Display image cache status
debug:
msg: "Cloud image {{ 'exists' if deploy_linux_vm_cloud_image_stat.stat.exists else 'not found' }}: {{ deploy_linux_vm_image_cache_path }}"
tags: [download]
- name: Check for manual download requirement
debug:
msg:
- "WARNING: {{ deploy_linux_vm_os_distribution }} requires manual download"
- "{{ deploy_linux_vm_distro_config.note | default('') }}"
- "Please download the image and place it at: {{ deploy_linux_vm_image_cache_path }}"
when:
- not deploy_linux_vm_cloud_image_stat.stat.exists
- deploy_linux_vm_distro_config.note is defined
tags: [download]
- name: Download cloud image
get_url:
url: "{{ deploy_linux_vm_distro_config.url }}"
dest: "{{ deploy_linux_vm_image_cache_path }}"
mode: '0644'
timeout: 1200
when:
- not deploy_linux_vm_cloud_image_stat.stat.exists
- deploy_linux_vm_distro_config.note is not defined
register: deploy_linux_vm_download_result
tags: [download]
- name: Download checksum file
get_url:
url: "{{ deploy_linux_vm_distro_config.checksum_url }}"
dest: "/tmp/{{ deploy_linux_vm_os_distribution }}-CHECKSUM"
mode: '0644'
when:
- deploy_linux_vm_distro_config.checksum_url is defined
- deploy_linux_vm_download_result is changed or deploy_linux_vm_cloud_image_stat.stat.exists
tags: [download, verify]
- name: Verify cloud image checksum (SHA512)
shell: |
cd {{ deploy_linux_vm_images_dir }}
grep "{{ deploy_linux_vm_distro_config.cache_name }}" /tmp/{{ deploy_linux_vm_os_distribution }}-CHECKSUM | sha512sum -c -
register: deploy_linux_vm_checksum_result
changed_when: false
when:
- deploy_linux_vm_distro_config.checksum_type is defined
- deploy_linux_vm_distro_config.checksum_type == "sha512"
- deploy_linux_vm_distro_config.checksum_url is defined
tags: [verify]
- name: Verify cloud image checksum (SHA256)
shell: |
cd {{ deploy_linux_vm_images_dir }}
grep "{{ deploy_linux_vm_distro_config.cache_name }}" /tmp/{{ deploy_linux_vm_os_distribution }}-CHECKSUM | sha256sum -c -
register: deploy_linux_vm_checksum_result
changed_when: false
when:
- deploy_linux_vm_distro_config.checksum_type is defined
- deploy_linux_vm_distro_config.checksum_type == "sha256"
- deploy_linux_vm_distro_config.checksum_url is defined
tags: [verify]
- name: Ensure image file exists before proceeding
stat:
path: "{{ deploy_linux_vm_image_cache_path }}"
register: deploy_linux_vm_final_image_check
failed_when: not deploy_linux_vm_final_image_check.stat.exists
tags: [verify]

View File

@@ -0,0 +1,64 @@
---
# =============================================================================
# Installation Tasks - Install Required Packages on Hypervisor
# =============================================================================
- name: Install required packages for VM deployment (Debian/Ubuntu)
apt:
name:
- libvirt-daemon-system
- libvirt-clients
- virtinst
- qemu-kvm
- qemu-utils
- cloud-image-utils
- genisoimage
- wget
- curl
- python3-libvirt
- lvm2
- parted
state: present
update_cache: yes
when: ansible_os_family == "Debian"
tags: [install]
- name: Install required packages for VM deployment (RHEL/CentOS)
dnf:
name:
- libvirt
- libvirt-client
- virt-install
- qemu-kvm
- qemu-img
- cloud-utils
- genisoimage
- wget
- curl
- python3-libvirt
- lvm2
- parted
state: present
when: ansible_os_family == "RedHat"
tags: [install]
- name: Ensure libvirtd service is running
systemd:
name: libvirtd
state: started
enabled: yes
tags: [install]
- name: Ensure default libvirt network is active
command: virsh net-start default
register: deploy_linux_vm_net_start
failed_when: false
changed_when: deploy_linux_vm_net_start.rc == 0
tags: [install]
- name: Ensure default libvirt network is autostarted
command: virsh net-autostart default
register: deploy_linux_vm_net_autostart
failed_when: false
changed_when: false
tags: [install]

View File

@@ -0,0 +1,185 @@
---
# =============================================================================
# LVM Configuration Tasks - Configure LVM on Deployed VM
# =============================================================================
# This task file configures LVM on the deployed VM per CLAUDE.md requirements
- name: Wait for cloud-init to complete on VM
delegate_to: "{{ deploy_linux_vm_ip }}"
command: cloud-init status --wait
changed_when: false
failed_when: false
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Install LVM packages on VM (Debian/Ubuntu)
delegate_to: "{{ deploy_linux_vm_ip }}"
apt:
name:
- lvm2
- parted
state: present
update_cache: yes
when: deploy_linux_vm_distro_config.family == "debian"
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Install LVM packages on VM (RHEL)
delegate_to: "{{ deploy_linux_vm_ip }}"
dnf:
name:
- lvm2
- parted
state: present
when: deploy_linux_vm_distro_config.family == "rhel"
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Create Physical Volume on {{ deploy_linux_vm_lvm_pv_device }}
delegate_to: "{{ deploy_linux_vm_ip }}"
command: pvcreate {{ deploy_linux_vm_lvm_pv_device }}
register: deploy_linux_vm_pvcreate_result
failed_when: false
changed_when: deploy_linux_vm_pvcreate_result.rc == 0
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Create Volume Group {{ deploy_linux_vm_lvm_vg_name }}
delegate_to: "{{ deploy_linux_vm_ip }}"
lvg:
vg: "{{ deploy_linux_vm_lvm_vg_name }}"
pvs: "{{ deploy_linux_vm_lvm_pv_device }}"
state: present
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Create Logical Volumes
delegate_to: "{{ deploy_linux_vm_ip }}"
lvol:
vg: "{{ deploy_linux_vm_lvm_vg_name }}"
lv: "{{ item.name }}"
size: "{{ item.size }}"
state: present
loop: "{{ deploy_linux_vm_lvm_volumes }}"
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Create filesystems on logical volumes (non-swap)
delegate_to: "{{ deploy_linux_vm_ip }}"
filesystem:
fstype: "{{ item.fstype }}"
dev: "/dev/{{ deploy_linux_vm_lvm_vg_name }}/{{ item.name }}"
loop: "{{ deploy_linux_vm_lvm_volumes }}"
when: item.fstype != "swap"
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Create swap filesystem
delegate_to: "{{ deploy_linux_vm_ip }}"
command: mkswap /dev/{{ deploy_linux_vm_lvm_vg_name }}/{{ item.name }}
loop: "{{ deploy_linux_vm_lvm_volumes }}"
when: item.fstype == "swap"
register: deploy_linux_vm_mkswap_result
failed_when: false
changed_when: deploy_linux_vm_mkswap_result.rc == 0
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Create temporary mount points
delegate_to: "{{ deploy_linux_vm_ip }}"
file:
path: "/mnt/{{ item.name | regex_replace('^lv_', '') | regex_replace('_', '-') }}"
state: directory
mode: '0755'
loop: "{{ deploy_linux_vm_lvm_volumes }}"
when: item.fstype != "swap"
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Mount logical volumes temporarily for data copy
delegate_to: "{{ deploy_linux_vm_ip }}"
mount:
path: "/mnt/{{ item.name | regex_replace('^lv_', '') | regex_replace('_', '-') }}"
src: "/dev/{{ deploy_linux_vm_lvm_vg_name }}/{{ item.name }}"
fstype: "{{ item.fstype }}"
state: mounted
loop: "{{ deploy_linux_vm_lvm_volumes }}"
when: item.fstype != "swap"
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Copy existing data to LVM volumes
delegate_to: "{{ deploy_linux_vm_ip }}"
shell: |
if [ -d "{{ item.mount }}" ] && [ "$(ls -A {{ item.mount }})" ]; then
rsync -av {{ item.mount }}/ /mnt/{{ item.name | regex_replace('^lv_', '') | regex_replace('_', '-') }}/
fi
loop: "{{ deploy_linux_vm_lvm_volumes }}"
when:
- item.fstype != "swap"
- item.mount != "none"
register: deploy_linux_vm_rsync_result
failed_when: false
changed_when: deploy_linux_vm_rsync_result.rc == 0
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Unmount temporary mount points
delegate_to: "{{ deploy_linux_vm_ip }}"
mount:
path: "/mnt/{{ item.name | regex_replace('^lv_', '') | regex_replace('_', '-') }}"
state: unmounted
loop: "{{ deploy_linux_vm_lvm_volumes }}"
when: item.fstype != "swap"
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Update /etc/fstab with LVM mounts
delegate_to: "{{ deploy_linux_vm_ip }}"
lineinfile:
path: /etc/fstab
line: "/dev/{{ deploy_linux_vm_lvm_vg_name }}/{{ item.name }} {{ item.mount }} {{ item.fstype }} {{ item.mount_options }} 0 {{ '0' if item.fstype == 'swap' else '2' }}"
state: present
loop: "{{ deploy_linux_vm_lvm_volumes }}"
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
tags: [lvm, post-deploy]
- name: Display LVM configuration summary
debug:
msg:
- "=== LVM Configuration Complete ==="
- "Volume Group: {{ deploy_linux_vm_lvm_vg_name }}"
- "Physical Volume: {{ deploy_linux_vm_lvm_pv_device }} (30GB)"
- "Logical Volumes: {{ deploy_linux_vm_lvm_volumes | length }}"
- ""
- "⚠️ IMPORTANT: VM needs reboot to use new mounts"
- "After reboot, LVM volumes will be mounted automatically"
- ""
- "To reboot: ssh {{ deploy_linux_vm_ansible_user }}@{{ deploy_linux_vm_ip }} 'sudo reboot'"
tags: [lvm, post-deploy]

View File

@@ -0,0 +1,42 @@
---
# =============================================================================
# Deploy Linux VM Role - Main Tasks
# =============================================================================
- name: Include validation tasks
include_tasks: validate.yml
tags: [validate, preflight, always]
- name: Include installation tasks
include_tasks: install.yml
tags: [install]
- name: Include download tasks
include_tasks: download.yml
tags: [download]
- name: Include storage tasks
include_tasks: storage.yml
tags: [storage]
- name: Include cloud-init tasks
include_tasks: cloud-init.yml
tags: [cloud-init]
- name: Include deployment tasks
include_tasks: deploy.yml
tags: [deploy]
- name: Include LVM configuration tasks
include_tasks: lvm.yml
when: deploy_linux_vm_use_lvm | bool
tags: [lvm, post-deploy]
- name: Include validation tasks (post-deployment)
include_tasks: post-validate.yml
tags: [validate, post-deploy]
- name: Include cleanup tasks
include_tasks: cleanup.yml
when: deploy_linux_vm_cleanup_temp_files | bool
tags: [cleanup]

View File

@@ -0,0 +1,82 @@
---
# =============================================================================
# Post-Validation Tasks - Validate Deployed VM
# =============================================================================
- name: Get VM details
command: virsh dominfo {{ deploy_linux_vm_name }}
register: deploy_linux_vm_details
changed_when: false
tags: [validate, post-deploy]
- name: Display VM details
debug:
var: deploy_linux_vm_details.stdout_lines
tags: [validate, post-deploy]
- name: Check VM is running
command: virsh list --name
register: deploy_linux_vm_running_vms
changed_when: false
failed_when: deploy_linux_vm_name not in deploy_linux_vm_running_vms.stdout_lines
tags: [validate, post-deploy]
- name: Validate SSH connectivity
wait_for:
host: "{{ deploy_linux_vm_ip }}"
port: 22
timeout: 60
state: started
when: not deploy_linux_vm_skip_validation
tags: [validate, post-deploy]
- name: Gather system information from VM
delegate_to: "{{ deploy_linux_vm_ip }}"
setup:
register: deploy_linux_vm_facts
vars:
ansible_user: "{{ deploy_linux_vm_ansible_user }}"
ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'
when: not deploy_linux_vm_skip_validation
tags: [validate, post-deploy]
- name: Display VM system information
debug:
msg:
- "=== System Information ==="
- "OS: {{ deploy_linux_vm_facts.ansible_facts.ansible_distribution }} {{ deploy_linux_vm_facts.ansible_facts.ansible_distribution_version }}"
- "Kernel: {{ deploy_linux_vm_facts.ansible_facts.ansible_kernel }}"
- "Architecture: {{ deploy_linux_vm_facts.ansible_facts.ansible_architecture }}"
- "Hostname: {{ deploy_linux_vm_facts.ansible_facts.ansible_hostname }}"
- "FQDN: {{ deploy_linux_vm_facts.ansible_facts.ansible_fqdn }}"
- "Python: {{ deploy_linux_vm_facts.ansible_facts.ansible_python_version }}"
when: not deploy_linux_vm_skip_validation
tags: [validate, post-deploy]
- name: Display deployment summary
debug:
msg:
- "╔════════════════════════════════════════════════════════════════╗"
- "║ VM Deployment Successfully Completed ║"
- "╚════════════════════════════════════════════════════════════════╝"
- ""
- "VM Details:"
- " Name: {{ deploy_linux_vm_name }}"
- " Distribution: {{ deploy_linux_vm_os_distribution }}"
- " IP Address: {{ deploy_linux_vm_ip }}"
- " Resources: {{ deploy_linux_vm_vcpus }} vCPUs, {{ deploy_linux_vm_memory_mb }}MB RAM, {{ deploy_linux_vm_disk_size_gb }}GB Disk"
- " LVM: {{ 'Enabled' if deploy_linux_vm_use_lvm else 'Disabled' }}"
- ""
- "Access:"
- " ssh {{ deploy_linux_vm_ansible_user }}@{{ deploy_linux_vm_ip }}"
- " ssh -J {{ inventory_hostname }} {{ deploy_linux_vm_ansible_user }}@{{ deploy_linux_vm_ip }}"
- ""
- "Add to Ansible inventory:"
- " {{ deploy_linux_vm_name }}:"
- " ansible_host: {{ deploy_linux_vm_ip }}"
- " ansible_user: {{ deploy_linux_vm_ansible_user }}"
- " ansible_ssh_common_args: '-o ProxyJump={{ inventory_hostname }} -o StrictHostKeyChecking=accept-new'"
- " os_distribution: {{ deploy_linux_vm_os_distribution }}"
- " os_family: {{ deploy_linux_vm_distro_config.family }}"
- ""
tags: [validate, post-deploy]

View File

@@ -0,0 +1,64 @@
---
# =============================================================================
# Storage Tasks - Create VM Disk Images
# =============================================================================
- name: Create primary VM disk from cloud image
command: >
qemu-img create -f qcow2 -F qcow2
-b {{ deploy_linux_vm_image_cache_path }}
{{ deploy_linux_vm_disk_path }}
{{ deploy_linux_vm_disk_size_gb }}G
args:
creates: "{{ deploy_linux_vm_disk_path }}"
tags: [storage]
- name: Set proper permissions on VM disk (Debian/Ubuntu)
file:
path: "{{ deploy_linux_vm_disk_path }}"
owner: libvirt-qemu
group: kvm
mode: '0600'
when: ansible_os_family == "Debian"
tags: [storage]
- name: Set proper permissions on VM disk (RHEL)
file:
path: "{{ deploy_linux_vm_disk_path }}"
owner: qemu
group: qemu
mode: '0600'
when: ansible_os_family == "RedHat"
tags: [storage]
- name: Create LVM data disk for VM
command: >
qemu-img create -f qcow2
{{ deploy_linux_vm_images_dir }}/{{ deploy_linux_vm_name }}-lvm.qcow2
30G
args:
creates: "{{ deploy_linux_vm_images_dir }}/{{ deploy_linux_vm_name }}-lvm.qcow2"
when: deploy_linux_vm_use_lvm | bool
tags: [storage, lvm]
- name: Set proper permissions on LVM disk (Debian/Ubuntu)
file:
path: "{{ deploy_linux_vm_images_dir }}/{{ deploy_linux_vm_name }}-lvm.qcow2"
owner: libvirt-qemu
group: kvm
mode: '0600'
when:
- deploy_linux_vm_use_lvm | bool
- ansible_os_family == "Debian"
tags: [storage, lvm]
- name: Set proper permissions on LVM disk (RHEL)
file:
path: "{{ deploy_linux_vm_images_dir }}/{{ deploy_linux_vm_name }}-lvm.qcow2"
owner: qemu
group: qemu
mode: '0600'
when:
- deploy_linux_vm_use_lvm | bool
- ansible_os_family == "RedHat"
tags: [storage, lvm]

View File

@@ -0,0 +1,82 @@
---
# =============================================================================
# Validation Tasks - Pre-flight Checks
# =============================================================================
- name: Validate distribution selection
assert:
that:
- deploy_linux_vm_os_distribution is defined
- deploy_linux_vm_os_distribution in deploy_linux_vm_cloud_images.keys()
fail_msg: |
Invalid distribution '{{ deploy_linux_vm_os_distribution }}'.
Supported distributions: {{ deploy_linux_vm_cloud_images.keys() | list | join(', ') }}
success_msg: "Distribution '{{ deploy_linux_vm_os_distribution }}' is valid"
tags: [validate, preflight]
- name: Set distribution facts
set_fact:
deploy_linux_vm_distro_config: "{{ deploy_linux_vm_cloud_images[deploy_linux_vm_os_distribution] }}"
deploy_linux_vm_image_cache_path: "{{ deploy_linux_vm_images_dir }}/{{ deploy_linux_vm_cloud_images[deploy_linux_vm_os_distribution].cache_name }}"
tags: [always]
- name: Display deployment information
debug:
msg:
- "=== VM Deployment Configuration ==="
- "VM Name: {{ deploy_linux_vm_name }}"
- "Distribution: {{ deploy_linux_vm_os_distribution }}"
- "OS Family: {{ deploy_linux_vm_distro_config.family }}"
- "Package Manager: {{ deploy_linux_vm_distro_config.package_manager }}"
- "vCPUs: {{ deploy_linux_vm_vcpus }}"
- "Memory: {{ deploy_linux_vm_memory_mb }} MB"
- "Disk: {{ deploy_linux_vm_disk_size_gb }} GB"
- "LVM Enabled: {{ deploy_linux_vm_use_lvm }}"
tags: [validate, preflight]
- name: Validate VM name
assert:
that:
- deploy_linux_vm_name is defined
- deploy_linux_vm_name | length > 0
- deploy_linux_vm_name is match('^[a-zA-Z0-9_-]+$')
fail_msg: "VM name must be defined and contain only alphanumeric characters, hyphens, or underscores"
success_msg: "VM name '{{ deploy_linux_vm_name }}' is valid"
tags: [validate, preflight]
- name: Check if VM already exists
command: virsh dominfo {{ deploy_linux_vm_name }}
register: deploy_linux_vm_exists_check
failed_when: false
changed_when: false
tags: [validate, preflight]
- name: Fail if VM already exists
fail:
msg: "VM '{{ deploy_linux_vm_name }}' already exists on hypervisor. Please choose a different name or destroy the existing VM."
when: deploy_linux_vm_exists_check.rc == 0
tags: [validate, preflight]
- name: Verify virtualization support
command: virt-host-validate qemu
register: deploy_linux_vm_virt_validation
failed_when: false
changed_when: false
tags: [validate, preflight]
- name: Display virtualization validation results
debug:
var: deploy_linux_vm_virt_validation.stdout_lines
tags: [validate, preflight]
- name: Validate LVM configuration
assert:
that:
- deploy_linux_vm_lvm_vg_name is defined
- deploy_linux_vm_lvm_pv_device is defined
- deploy_linux_vm_lvm_volumes is defined
- deploy_linux_vm_lvm_volumes | length > 0
fail_msg: "LVM is enabled but configuration is incomplete"
success_msg: "LVM configuration is valid"
when: deploy_linux_vm_use_lvm | bool
tags: [validate, preflight, lvm]

View File

@@ -0,0 +1,2 @@
instance-id: {{ deploy_linux_vm_name }}
local-hostname: {{ deploy_linux_vm_hostname }}

View File

@@ -0,0 +1,121 @@
#cloud-config
hostname: {{ deploy_linux_vm_hostname }}
fqdn: {{ deploy_linux_vm_hostname }}.{{ deploy_linux_vm_domain }}
manage_etc_hosts: true
# Create ansible user with sudo privileges
users:
- name: {{ deploy_linux_vm_ansible_user }}
groups: sudo
shell: {{ deploy_linux_vm_ansible_user_shell }}
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- {{ deploy_linux_vm_ansible_user_ssh_key }}
- name: root
lock_passwd: false
# Set root password (for emergency console access)
chpasswd:
list: |
root:{{ deploy_linux_vm_root_password }}
expire: false
# SSH configuration
ssh_pwauth: false
disable_root: false
# Install essential packages per CLAUDE.md guidelines
packages:
- sudo
{% for package in deploy_linux_vm_essential_packages %}
- {{ package }}
{% endfor %}
{% for package in deploy_linux_vm_security_packages %}
- {{ package }}
{% endfor %}
- auditd
- ufw
- lvm2
- cloud-guest-utils
- parted
- unattended-upgrades
- apt-listchanges
# Security configuration files
write_files:
- path: /etc/ssh/sshd_config.d/99-security.conf
content: |
# SSH Security Configuration - CLAUDE.md Compliance
PermitRootLogin {{ deploy_linux_vm_ssh_permit_root_login }}
PasswordAuthentication {{ deploy_linux_vm_ssh_password_authentication }}
PubkeyAuthentication {{ deploy_linux_vm_ssh_pubkey_authentication }}
MaxAuthTries {{ deploy_linux_vm_ssh_max_auth_tries }}
MaxSessions {{ deploy_linux_vm_ssh_max_sessions }}
ClientAliveInterval {{ deploy_linux_vm_ssh_client_alive_interval }}
ClientAliveCountMax {{ deploy_linux_vm_ssh_client_alive_count_max }}
# Disable GSSAPI Authentication (per requirements)
GSSAPIAuthentication {{ deploy_linux_vm_ssh_gssapi_authentication }}
GSSAPICleanupCredentials {{ deploy_linux_vm_ssh_gssapi_cleanup_credentials }}
# Additional hardening
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding no
PrintMotd no
AcceptEnv LANG LC_*
permissions: '0644'
- path: /etc/sudoers.d/{{ deploy_linux_vm_ansible_user }}
content: |
{{ deploy_linux_vm_ansible_user }} ALL=(ALL) NOPASSWD:ALL
permissions: '0440'
- path: /etc/apt/apt.conf.d/50unattended-upgrades
content: |
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "{{ 'true' if deploy_linux_vm_automatic_reboot else 'false' }}";
permissions: '0644'
- path: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
permissions: '0644'
# System configuration commands
runcmd:
- systemctl enable ssh
- systemctl restart ssh
- systemctl enable chrony
- systemctl start chrony
{% if deploy_linux_vm_enable_firewall %}
- ufw --force enable
- ufw allow ssh
{% endif %}
{% if deploy_linux_vm_enable_auditd %}
- systemctl enable auditd
- systemctl start auditd
{% endif %}
- growpart /dev/vda 1 || true
- resize2fs /dev/vda1 || true
package_update: {{ deploy_linux_vm_package_update | lower }}
package_upgrade: {{ deploy_linux_vm_package_upgrade | lower }}
package_reboot_if_required: {{ deploy_linux_vm_package_reboot_if_required | lower }}
timezone: {{ deploy_linux_vm_timezone }}
locale: {{ deploy_linux_vm_locale }}
output:
all: '| tee -a /var/log/cloud-init-output.log'
final_message: "{{ deploy_linux_vm_os_distribution }} VM deployment completed. System is ready after $UPTIME seconds."

View File

@@ -0,0 +1,127 @@
#cloud-config
hostname: {{ deploy_linux_vm_hostname }}
fqdn: {{ deploy_linux_vm_hostname }}.{{ deploy_linux_vm_domain }}
manage_etc_hosts: true
# Create ansible user with sudo privileges
users:
- name: {{ deploy_linux_vm_ansible_user }}
groups: wheel
shell: {{ deploy_linux_vm_ansible_user_shell }}
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- {{ deploy_linux_vm_ansible_user_ssh_key }}
- name: root
lock_passwd: false
# Set root password (for emergency console access)
chpasswd:
list: |
root:{{ deploy_linux_vm_root_password }}
expire: false
# SSH configuration
ssh_pwauth: false
disable_root: false
# Install essential packages per CLAUDE.md guidelines
packages:
- sudo
{% for package in deploy_linux_vm_essential_packages %}
- {{ package }}
{% endfor %}
{% for package in deploy_linux_vm_security_packages %}
- {{ package }}
{% endfor %}
- audit
- firewalld
- lvm2
- cloud-utils-growpart
- gdisk
- dnf-automatic
- policycoreutils-python-utils
# Security configuration files
write_files:
- path: /etc/ssh/sshd_config.d/99-security.conf
content: |
# SSH Security Configuration - CLAUDE.md Compliance
PermitRootLogin {{ deploy_linux_vm_ssh_permit_root_login }}
PasswordAuthentication {{ deploy_linux_vm_ssh_password_authentication }}
PubkeyAuthentication {{ deploy_linux_vm_ssh_pubkey_authentication }}
MaxAuthTries {{ deploy_linux_vm_ssh_max_auth_tries }}
MaxSessions {{ deploy_linux_vm_ssh_max_sessions }}
ClientAliveInterval {{ deploy_linux_vm_ssh_client_alive_interval }}
ClientAliveCountMax {{ deploy_linux_vm_ssh_client_alive_count_max }}
# Disable GSSAPI Authentication (per requirements)
GSSAPIAuthentication {{ deploy_linux_vm_ssh_gssapi_authentication }}
GSSAPICleanupCredentials {{ deploy_linux_vm_ssh_gssapi_cleanup_credentials }}
# Additional hardening
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding no
permissions: '0644'
- path: /etc/sudoers.d/{{ deploy_linux_vm_ansible_user }}
content: |
{{ deploy_linux_vm_ansible_user }} ALL=(ALL) NOPASSWD:ALL
permissions: '0440'
- path: /etc/dnf/automatic.conf
content: |
[commands]
upgrade_type = security
download_updates = yes
apply_updates = {{ 'yes' if deploy_linux_vm_enable_automatic_updates else 'no' }}
[emitters]
emit_via = stdio
[email]
email_from = root@{{ deploy_linux_vm_hostname }}.{{ deploy_linux_vm_domain }}
[base]
debuglevel = 1
permissions: '0644'
# System configuration commands
runcmd:
- systemctl enable sshd
- systemctl restart sshd
- systemctl enable chronyd
- systemctl start chronyd
{% if deploy_linux_vm_enable_firewall %}
- systemctl enable firewalld
- systemctl start firewalld
- firewall-cmd --permanent --add-service=ssh
- firewall-cmd --reload
{% endif %}
{% if deploy_linux_vm_enable_auditd %}
- systemctl enable auditd
- systemctl start auditd
{% endif %}
{% if deploy_linux_vm_enable_automatic_updates %}
- systemctl enable dnf-automatic.timer
- systemctl start dnf-automatic.timer
{% endif %}
{% if deploy_linux_vm_enable_selinux %}
- setenforce 1
- sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
{% endif %}
- growpart /dev/vda 1 || true
- xfs_growfs / || resize2fs /dev/vda1 || true
package_update: {{ deploy_linux_vm_package_update | lower }}
package_upgrade: {{ deploy_linux_vm_package_upgrade | lower }}
package_reboot_if_required: {{ deploy_linux_vm_package_reboot_if_required | lower }}
timezone: {{ deploy_linux_vm_timezone }}
locale: {{ deploy_linux_vm_locale }}
output:
all: '| tee -a /var/log/cloud-init-output.log'
final_message: "{{ deploy_linux_vm_os_distribution }} VM deployment completed. System is ready after $UPTIME seconds."

View File

@@ -0,0 +1,100 @@
#cloud-config
hostname: {{ deploy_linux_vm_hostname }}
fqdn: {{ deploy_linux_vm_hostname }}.{{ deploy_linux_vm_domain }}
manage_etc_hosts: true
# Create ansible user with sudo privileges
users:
- name: {{ deploy_linux_vm_ansible_user }}
groups: wheel
shell: {{ deploy_linux_vm_ansible_user_shell }}
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- {{ deploy_linux_vm_ansible_user_ssh_key }}
- name: root
lock_passwd: false
# Set root password (for emergency console access)
chpasswd:
list: |
root:{{ deploy_linux_vm_root_password }}
expire: false
# SSH configuration
ssh_pwauth: false
disable_root: false
# Install essential packages
packages:
- sudo
{% for package in deploy_linux_vm_essential_packages %}
- {{ package }}
{% endfor %}
{% for package in deploy_linux_vm_security_packages %}
- {{ package }}
{% endfor %}
- audit
- firewalld
- lvm2
- cloud-utils-growpart
- gdisk
# Security configuration files
write_files:
- path: /etc/ssh/sshd_config.d/99-security.conf
content: |
# SSH Security Configuration - CLAUDE.md Compliance
PermitRootLogin {{ deploy_linux_vm_ssh_permit_root_login }}
PasswordAuthentication {{ deploy_linux_vm_ssh_password_authentication }}
PubkeyAuthentication {{ deploy_linux_vm_ssh_pubkey_authentication }}
MaxAuthTries {{ deploy_linux_vm_ssh_max_auth_tries }}
MaxSessions {{ deploy_linux_vm_ssh_max_sessions }}
ClientAliveInterval {{ deploy_linux_vm_ssh_client_alive_interval }}
ClientAliveCountMax {{ deploy_linux_vm_ssh_client_alive_count_max }}
# Disable GSSAPI Authentication (per requirements)
GSSAPIAuthentication {{ deploy_linux_vm_ssh_gssapi_authentication }}
GSSAPICleanupCredentials {{ deploy_linux_vm_ssh_gssapi_cleanup_credentials }}
# Additional hardening
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding no
permissions: '0644'
- path: /etc/sudoers.d/{{ deploy_linux_vm_ansible_user }}
content: |
{{ deploy_linux_vm_ansible_user }} ALL=(ALL) NOPASSWD:ALL
permissions: '0440'
# System configuration commands
runcmd:
- systemctl enable sshd
- systemctl restart sshd
- systemctl enable chronyd
- systemctl start chronyd
{% if deploy_linux_vm_enable_firewall %}
- systemctl enable firewalld
- systemctl start firewalld
- firewall-cmd --permanent --add-service=ssh
- firewall-cmd --reload
{% endif %}
{% if deploy_linux_vm_enable_auditd %}
- systemctl enable auditd
- systemctl start auditd
{% endif %}
- growpart /dev/vda 1 || true
- xfs_growfs / || resize2fs /dev/vda1 || btrfs filesystem resize max / || true
package_update: {{ deploy_linux_vm_package_update | lower }}
package_upgrade: {{ deploy_linux_vm_package_upgrade | lower }}
package_reboot_if_required: {{ deploy_linux_vm_package_reboot_if_required | lower }}
timezone: {{ deploy_linux_vm_timezone }}
locale: {{ deploy_linux_vm_locale }}
output:
all: '| tee -a /var/log/cloud-init-output.log'
final_message: "{{ deploy_linux_vm_os_distribution }} VM deployment completed. System is ready after $UPTIME seconds."

View File

@@ -0,0 +1,157 @@
---
# =============================================================================
# Deploy Linux VM Role - Role Variables
# =============================================================================
# -----------------------------------------------------------------------------
# Cloud Image Repository Configuration
# -----------------------------------------------------------------------------
deploy_linux_vm_cloud_images:
# Debian
debian-11:
url: "https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-generic-amd64.qcow2"
checksum_url: "https://cloud.debian.org/images/cloud/bullseye/latest/SHA512SUMS"
checksum_type: "sha512"
os_variant: "debian11"
cache_name: "debian-11-generic-amd64.qcow2"
package_manager: "apt"
family: "debian"
debian-12:
url: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
checksum_url: "https://cloud.debian.org/images/cloud/bookworm/latest/SHA512SUMS"
checksum_type: "sha512"
os_variant: "debian12"
cache_name: "debian-12-generic-amd64.qcow2"
package_manager: "apt"
family: "debian"
# Ubuntu
ubuntu-20.04:
url: "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img"
checksum_url: "https://cloud-images.ubuntu.com/focal/current/SHA256SUMS"
checksum_type: "sha256"
os_variant: "ubuntu20.04"
cache_name: "ubuntu-20.04-server-cloudimg-amd64.img"
package_manager: "apt"
family: "debian"
ubuntu-22.04:
url: "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
checksum_url: "https://cloud-images.ubuntu.com/jammy/current/SHA256SUMS"
checksum_type: "sha256"
os_variant: "ubuntu22.04"
cache_name: "ubuntu-22.04-server-cloudimg-amd64.img"
package_manager: "apt"
family: "debian"
ubuntu-24.04:
url: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
checksum_url: "https://cloud-images.ubuntu.com/noble/current/SHA256SUMS"
checksum_type: "sha256"
os_variant: "ubuntu24.04"
cache_name: "ubuntu-24.04-server-cloudimg-amd64.img"
package_manager: "apt"
family: "debian"
# RHEL (requires subscription)
rhel-8:
url: "https://access.redhat.com/downloads/content/rhel/8/x86_64/latest/rhel-8-x86_64-kvm.qcow2"
os_variant: "rhel8.0"
cache_name: "rhel-8-x86_64-kvm.qcow2"
package_manager: "dnf"
family: "rhel"
note: "Requires Red Hat subscription and manual download"
rhel-9:
url: "https://access.redhat.com/downloads/content/rhel/9/x86_64/latest/rhel-9-x86_64-kvm.qcow2"
os_variant: "rhel9.0"
cache_name: "rhel-9-x86_64-kvm.qcow2"
package_manager: "dnf"
family: "rhel"
note: "Requires Red Hat subscription and manual download"
# CentOS Stream
centos-stream-8:
url: "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2"
checksum_url: "https://cloud.centos.org/centos/8-stream/x86_64/images/CHECKSUM"
checksum_type: "sha256"
os_variant: "centos-stream8"
cache_name: "centos-stream-8-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
centos-stream-9:
url: "https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2"
checksum_url: "https://cloud.centos.org/centos/9-stream/x86_64/images/CHECKSUM"
checksum_type: "sha256"
os_variant: "centos-stream9"
cache_name: "centos-stream-9-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
# Rocky Linux
rocky-8:
url: "https://download.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud-Base.latest.x86_64.qcow2"
checksum_url: "https://download.rockylinux.org/pub/rocky/8/images/x86_64/CHECKSUM"
checksum_type: "sha256"
os_variant: "rocky8"
cache_name: "rocky-8-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
rocky-9:
url: "https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"
checksum_url: "https://download.rockylinux.org/pub/rocky/9/images/x86_64/CHECKSUM"
checksum_type: "sha256"
os_variant: "rocky9"
cache_name: "rocky-9-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
# AlmaLinux
almalinux-8:
url: "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2"
checksum_url: "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/CHECKSUM"
checksum_type: "sha256"
os_variant: "almalinux8"
cache_name: "almalinux-8-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
almalinux-9:
url: "https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2"
checksum_url: "https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/CHECKSUM"
checksum_type: "sha256"
os_variant: "almalinux9"
cache_name: "almalinux-9-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
# SLES (requires registration)
sles-15:
url: "https://download.suse.com/Download?buildid=XXXXX"
os_variant: "sles15"
cache_name: "sles-15-genericcloud-amd64.qcow2"
package_manager: "zypper"
family: "suse"
note: "Requires SUSE subscription and manual download"
# openSUSE Leap
opensuse-leap-15.5:
url: "https://download.opensuse.org/distribution/leap/15.5/appliances/openSUSE-Leap-15.5-Minimal-VM.x86_64-Cloud.qcow2"
checksum_url: "https://download.opensuse.org/distribution/leap/15.5/appliances/openSUSE-Leap-15.5-Minimal-VM.x86_64-Cloud.qcow2.sha256"
checksum_type: "sha256"
os_variant: "opensuse15.5"
cache_name: "opensuse-leap-15.5-minimal-vm-amd64.qcow2"
package_manager: "zypper"
family: "suse"
opensuse-leap-15.6:
url: "https://download.opensuse.org/distribution/leap/15.6/appliances/openSUSE-Leap-15.6-Minimal-VM.x86_64-Cloud.qcow2"
checksum_url: "https://download.opensuse.org/distribution/leap/15.6/appliances/openSUSE-Leap-15.6-Minimal-VM.x86_64-Cloud.qcow2.sha256"
checksum_type: "sha256"
os_variant: "opensuse15.6"
cache_name: "opensuse-leap-15.6-minimal-vm-amd64.qcow2"
package_manager: "zypper"
family: "suse"