- 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
533 lines
21 KiB
YAML
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]
|