Files
infra-automation/plays/deploy-debian-lvm-netinst.yml
Infrastructure Team 47df4035c3 Add LVM-enabled VM deployment playbooks
- Add deploy-debian-lvm-netinst.yml for Debian with native LVM
  - Uses network installer with preseed configuration
  - Full LVM partitioning per infrastructure guidelines
  - Creates vg_system with 8 logical volumes
  - Separate /boot, /opt, /tmp, /home, /var, /var/log, /var/tmp, /var/log/audit
  - Security mount options (noexec,nosuid,nodev on /tmp and /var/tmp)

- Add deploy-linux-vm-lvm.yml for multi-distro with post-config LVM
  - Supports all distributions from deploy-linux-vm.yml
  - Deploys VM with secondary 30GB disk for LVM
  - Post-deployment LVM configuration on /dev/vdb
  - Data migration from primary disk to LVM volumes
  - Automatic fstab updates
2025-11-10 22:51:40 +01:00

533 lines
21 KiB
YAML

---
# =============================================================================
# Debian VM Deployment with LVM using Network Installer
# =============================================================================
# Deploys Debian 12 with proper LVM partitioning per CLAUDE.md
# Uses preseed for automated installation
# =============================================================================
- name: Deploy Debian 12 VM with LVM on KVM hypervisor
hosts: grokbox
gather_facts: yes
become: yes
vars:
# VM Configuration
vm_name: "debian12-guest"
vm_hostname: "debian12"
vm_domain: "localdomain"
vm_vcpus: 2
vm_memory_mb: 2048
vm_disk_size_gb: 40
# Network Configuration
vm_network: "default"
vm_bridge: "virbr0"
# Storage Configuration
vm_disk_path: "/var/lib/libvirt/images/{{ vm_name }}.qcow2"
# Debian Network Installer
debian_netinst_url: "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.8.0-amd64-netinst.iso"
debian_netinst_path: "/var/lib/libvirt/images/debian-12.8.0-amd64-netinst.iso"
# Ansible User Configuration
ansible_user_ssh_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILBrnivsqjhAxWYeuuvnYc3neeRRuHsr2SjeKv+Drtpu user@debian"
tasks:
# =========================================================================
# Pre-flight Checks
# =========================================================================
- name: Check if VM already exists
command: virsh dominfo {{ vm_name }}
register: vm_exists
failed_when: false
changed_when: false
tags: [validate, preflight]
- name: Fail if VM already exists
fail:
msg: "VM '{{ vm_name }}' already exists. Destroy it first."
when: vm_exists.rc == 0
tags: [validate, preflight]
- name: Display deployment information
debug:
msg:
- "=== Debian 12 VM Deployment with LVM ==="
- "VM Name: {{ vm_name }}"
- "Method: Network installer with preseed"
- "Disk: {{ vm_disk_size_gb }} GB with LVM"
- "Installation time: ~10-15 minutes"
- ""
- "LVM Layout (per CLAUDE.md):"
- " VG: vg_system"
- " LVs: root(8G), opt(3G), tmp(1G), home(2G)"
- " var(5G), var_log(2G), var_tmp(5G), var_audit(1G), swap(2G)"
tags: [validate, preflight]
# =========================================================================
# Package Installation
# =========================================================================
- name: Install required packages
apt:
name:
- libvirt-daemon-system
- libvirt-clients
- virtinst
- qemu-kvm
- qemu-utils
- wget
- genisoimage
- python3-libvirt
state: present
tags: [install]
- name: Ensure libvirtd is running
systemd:
name: libvirtd
state: started
enabled: yes
tags: [install]
# =========================================================================
# Download Debian Network Installer
# =========================================================================
- name: Check if Debian netinst ISO exists
stat:
path: "{{ debian_netinst_path }}"
register: netinst_stat
tags: [download]
- name: Download Debian network installer ISO
get_url:
url: "{{ debian_netinst_url }}"
dest: "{{ debian_netinst_path }}"
mode: '0644'
timeout: 1200
when: not netinst_stat.stat.exists
tags: [download]
# =========================================================================
# Create Preseed Configuration
# =========================================================================
- name: Create preseed directory
file:
path: /tmp/preseed-{{ vm_name }}
state: directory
mode: '0755'
tags: [preseed]
- name: Create preseed configuration file
copy:
dest: /tmp/preseed-{{ vm_name }}/preseed.cfg
mode: '0644'
content: |
#### Debian 12 Preseed Configuration with LVM
#### Per CLAUDE.md Requirements
### Localization
d-i debian-installer/locale string en_US.UTF-8
d-i keyboard-configuration/xkb-keymap select us
### Network configuration
d-i netcfg/choose_interface select auto
d-i netcfg/get_hostname string {{ vm_hostname }}
d-i netcfg/get_domain string {{ vm_domain }}
d-i netcfg/wireless_wep string
### Mirror settings
d-i mirror/country string manual
d-i mirror/http/hostname string deb.debian.org
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
### Account setup
d-i passwd/root-login boolean true
d-i passwd/root-password password ChangeMe123!
d-i passwd/root-password-again password ChangeMe123!
# Create ansible user
d-i passwd/user-fullname string Ansible User
d-i passwd/username string ansible
d-i passwd/user-password password ansible123
d-i passwd/user-password-again password ansible123
d-i passwd/user-default-groups string sudo
### Clock and time zone
d-i clock-setup/utc boolean true
d-i time/zone string UTC
d-i clock-setup/ntp boolean true
### Partitioning with LVM
d-i partman-auto/method string lvm
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
# Create custom LVM recipe
d-i partman-auto/expert_recipe string \
boot-lvm :: \
512 512 512 ext4 \
$primary{ } $bootable{ } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ /boot } \
. \
8192 8192 8192 ext4 \
$defaultignore{ } \
$primary{ } \
method{ lvm } \
vg_name{ vg_system } \
. \
8192 8192 8192 ext4 \
$lvmok{ } \
lv_name{ lv_root } \
in_vg{ vg_system } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ / } \
. \
3072 3072 3072 ext4 \
$lvmok{ } \
lv_name{ lv_opt } \
in_vg{ vg_system } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ /opt } \
. \
1024 1024 1024 ext4 \
$lvmok{ } \
lv_name{ lv_tmp } \
in_vg{ vg_system } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ /tmp } \
options/noexec{ noexec } \
options/nosuid{ nosuid } \
options/nodev{ nodev } \
. \
2048 2048 2048 ext4 \
$lvmok{ } \
lv_name{ lv_home } \
in_vg{ vg_system } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ /home } \
. \
5120 5120 5120 ext4 \
$lvmok{ } \
lv_name{ lv_var } \
in_vg{ vg_system } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ /var } \
. \
2048 2048 2048 ext4 \
$lvmok{ } \
lv_name{ lv_var_log } \
in_vg{ vg_system } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ /var/log } \
. \
5120 5120 5120 ext4 \
$lvmok{ } \
lv_name{ lv_var_tmp } \
in_vg{ vg_system } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ /var/tmp } \
options/noexec{ noexec } \
options/nosuid{ nosuid } \
options/nodev{ nodev } \
. \
1024 1024 1024 ext4 \
$lvmok{ } \
lv_name{ lv_var_audit } \
in_vg{ vg_system } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ /var/log/audit } \
. \
2048 2048 2048 linux-swap \
$lvmok{ } \
lv_name{ lv_swap } \
in_vg{ vg_system } \
method{ swap } format{ } \
.
d-i partman-auto-lvm/guided_size string max
d-i partman-auto-lvm/new_vg_name string vg_system
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
### Package selection
tasksel tasksel/first multiselect standard, ssh-server
d-i pkgsel/include string sudo vim htop tmux curl wget rsync git python3 python3-pip jq bc lvm2 aide auditd chrony ufw cloud-init
d-i pkgsel/upgrade select full-upgrade
popularity-contest popularity-contest/participate boolean false
### Boot loader
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i grub-installer/bootdev string default
### Finishing up
d-i finish-install/reboot_in_progress note
### Late command - configure ansible user and SSH
d-i preseed/late_command string \
in-target mkdir -p /home/ansible/.ssh; \
in-target chmod 700 /home/ansible/.ssh; \
in-target sh -c 'echo "{{ ansible_user_ssh_key }}" > /home/ansible/.ssh/authorized_keys'; \
in-target chown -R ansible:ansible /home/ansible/.ssh; \
in-target chmod 600 /home/ansible/.ssh/authorized_keys; \
in-target sh -c 'echo "ansible ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ansible'; \
in-target chmod 440 /etc/sudoers.d/ansible; \
in-target mkdir -p /etc/ssh/sshd_config.d; \
in-target sh -c 'echo -e "PermitRootLogin no\nPasswordAuthentication no\nPubkeyAuthentication yes" > /etc/ssh/sshd_config.d/99-security.conf'
tags: [preseed]
- name: Create preseed ISO
command: >
genisoimage -r -J -o /tmp/preseed-{{ vm_name }}.iso
-V "PRESEED" /tmp/preseed-{{ vm_name }}/
args:
creates: /tmp/preseed-{{ vm_name }}.iso
tags: [preseed]
# =========================================================================
# Create VM Disk
# =========================================================================
- name: Create VM disk
command: >
qemu-img create -f qcow2
{{ vm_disk_path }}
{{ vm_disk_size_gb }}G
args:
creates: "{{ vm_disk_path }}"
tags: [storage]
- name: Set permissions on VM disk
file:
path: "{{ vm_disk_path }}"
owner: libvirt-qemu
group: kvm
mode: '0600'
tags: [storage]
# =========================================================================
# Install VM
# =========================================================================
- name: Display installation notice
debug:
msg:
- "=== Starting Debian Network Installation ==="
- "This will take approximately 10-15 minutes"
- "The VM will automatically install and configure:"
- " - Debian 12 (Bookworm)"
- " - LVM partitioning per CLAUDE.md"
- " - ansible user with SSH keys"
- " - Essential packages"
- ""
- "You can monitor progress with:"
- " ssh grokbox 'sudo virsh console {{ vm_name }}'"
tags: [install-vm]
- name: Create VM with network installer
command: >
virt-install
--name {{ vm_name }}
--memory {{ vm_memory_mb }}
--vcpus {{ vm_vcpus }}
--disk path={{ vm_disk_path }},format=qcow2,bus=virtio
--cdrom {{ debian_netinst_path }}
--disk path=/tmp/preseed-{{ vm_name }}.iso,device=cdrom
--network network={{ vm_network }},model=virtio
--os-variant debian12
--graphics none
--console pty,target_type=serial
--extra-args "auto=true priority=critical console=ttyS0,115200n8 serial preseed/file=/cdrom/preseed.cfg"
--noautoconsole
tags: [install-vm]
- name: Wait for installation to complete
pause:
minutes: 15
prompt: "Waiting for Debian installation (15 minutes)... You can monitor with: ssh grokbox 'sudo virsh console {{ vm_name }}'"
tags: [install-vm]
- name: Check if VM is running
command: virsh domstate {{ vm_name }}
register: vm_state
changed_when: false
tags: [validate]
- name: Display VM state
debug:
var: vm_state.stdout
tags: [validate]
- name: Start VM if not running
command: virsh start {{ vm_name }}
when: vm_state.stdout != "running"
failed_when: false
tags: [validate]
- name: Wait a bit for network
pause:
seconds: 30
tags: [validate]
- name: Get VM IP address
shell: |
virsh domifaddr {{ vm_name }} | grep -oP '(\d{1,3}\.){3}\d{1,3}' | head -1
register: vm_ip
retries: 10
delay: 10
until: vm_ip.stdout != ""
changed_when: false
tags: [validate]
- name: Set VM IP fact
set_fact:
deployed_vm_ip: "{{ vm_ip.stdout }}"
tags: [validate]
- name: Display VM information
debug:
msg:
- "=== VM Installation Complete ==="
- "VM Name: {{ vm_name }}"
- "IP Address: {{ deployed_vm_ip }}"
- "Access: ssh ansible@{{ deployed_vm_ip }}"
- "Or: ssh -J grokbox ansible@{{ deployed_vm_ip }}"
- ""
- "Root password: ChangeMe123! (change immediately!)"
- "Ansible password: ansible123 (SSH keys configured)"
tags: [validate]
- name: Cleanup preseed files
file:
path: "{{ item }}"
state: absent
loop:
- /tmp/preseed-{{ vm_name }}
- /tmp/preseed-{{ vm_name }}.iso
tags: [cleanup]
# =============================================================================
# Validate LVM Configuration
# =============================================================================
- name: Validate LVM configuration on deployed VM
hosts: "{{ hostvars['grokbox']['deployed_vm_ip'] }}"
gather_facts: yes
become: yes
vars:
ansible_user: ansible
ansible_password: ansible123
ansible_ssh_common_args: '-o ProxyJump=grokbox -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null'
ansible_python_interpreter: /usr/bin/python3
tasks:
- name: Wait for SSH to be available
wait_for_connection:
timeout: 300
tags: [validate-lvm]
- name: Gather system facts
setup:
tags: [validate-lvm]
- name: Display system information
debug:
msg:
- "=== System Information ==="
- "Hostname: {{ ansible_hostname }}"
- "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- "Kernel: {{ ansible_kernel }}"
tags: [validate-lvm]
- name: Check LVM configuration
command: "{{ item }}"
register: lvm_checks
changed_when: false
loop:
- pvdisplay
- vgdisplay
- lvdisplay
tags: [validate-lvm]
- name: Display LVM configuration
debug:
msg: "{{ item.stdout_lines }}"
loop: "{{ lvm_checks.results }}"
tags: [validate-lvm]
- name: Check filesystem layout
command: df -h
register: df_output
changed_when: false
tags: [validate-lvm]
- name: Display filesystem layout
debug:
var: df_output.stdout_lines
tags: [validate-lvm]
- name: Verify mount options for /tmp
shell: mount | grep ' /tmp '
register: tmp_mount
changed_when: false
tags: [validate-lvm]
- name: Display /tmp mount options
debug:
msg: "{{ tmp_mount.stdout }}"
tags: [validate-lvm]
- name: Check installed packages
command: dpkg -l
register: packages
changed_when: false
tags: [validate-lvm]
- name: Verify essential packages are installed
shell: dpkg -l | grep -E 'aide|auditd|chrony|ufw|lvm2' | awk '{print $2, $3}'
register: essential_pkgs
changed_when: false
tags: [validate-lvm]
- name: Display essential packages
debug:
var: essential_pkgs.stdout_lines
tags: [validate-lvm]
- name: Final validation summary
debug:
msg:
- "=== Validation Complete ==="
- "✓ VM deployed with Debian 12"
- "✓ LVM configuration applied"
- "✓ Volume group: vg_system"
- "✓ Logical volumes created per CLAUDE.md"
- "✓ Ansible user configured with SSH keys"
- "✓ Essential packages installed"
- ""
- "Next steps:"
- " 1. Change root password: ssh ansible@{{ ansible_default_ipv4.address }} sudo passwd root"
- " 2. Configure firewall: sudo ufw enable && sudo ufw allow ssh"
- " 3. Enable services: sudo systemctl enable --now chrony auditd"
- " 4. Run security hardening playbooks"
tags: [validate-lvm]