Establish clean homelab infrastructure baseline
lint / yaml (push) Has been cancelled
lint / ansible (push) Has been cancelled
lint / terraform (push) Has been cancelled

Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
This commit is contained in:
2026-09-09 16:47:20 +00:00
commit 88a02ababa
418 changed files with 50579 additions and 0 deletions
@@ -0,0 +1,51 @@
---
# windows_vm role — create a fresh Windows Server 2025 admin box from the ISO,
# fully unattended via autounattend.xml, WinRM-ready for join-windows.yml to finish.
#
# Uses virtio disk (viostor) + virtio NIC (NetKVM): the drivers are injected during
# Setup via autounattend DriverPaths (from the virtio-win ISO), and the full
# virtio-win guest tools (QEMU guest agent, balloon, …) are installed at first logon.
win_vm_name: "winadmin"
win_vm_vcpus: 4
win_vm_memory_mb: 6144
win_vm_disk_gb: 60
# libvirt placement — zvol root disk, presented as SATA (AHCI, native Windows driver).
win_vm_zvol_parent: "data/vm"
win_vm_zvol: "{{ win_vm_zvol_parent }}/{{ win_vm_name }}"
win_vm_zvol_dev: "/dev/zvol/{{ win_vm_zvol }}"
win_vm_zvol_volblocksize: "16K"
win_vm_bridge: "br0"
# Install media (already in your iso pool)
win_vm_iso: "/mnt/pool/proxmox/template/iso/zh-cn_windows_server_2025_updated_aug_2025_x64_dvd_9236d79b.iso"
# virtio-win drivers + guest tools — injected during Setup, guest tools run at first logon
win_vm_virtio_iso: "/mnt/pool/proxmox/template/iso/virtio-win-1.9.45.iso"
win_vm_virtio_ver: "2k25" # driver subfolder for Server 2025
# Edition to install from install.wim. VERIFY with:
# dism /Get-WimInfo /WimFile:<mounted>\sources\install.wim
# Typical Server 2025 indexes: 1=Std Core, 2=Std Desktop, 3=DC Core, 4=DC Desktop.
win_vm_image_index: 2 # Standard (Desktop Experience) — GPMC needs Desktop
# GVLK (public KMS client key) matching the edition above; activation happens later
# in join-windows.yml against vlmcsd. Std=below; DC=D764K-2NDRG-47T6Q-P8T8W-YP6DF
win_vm_gvlk: "TVRH6-WHNXV-R9WG3-9XRFY-MY832"
# Locale (ISO is zh-CN)
win_vm_ui_language: "zh-CN"
win_vm_system_locale: "zh-CN"
win_vm_user_locale: "zh-CN"
win_vm_input_locale: "0409:00000409;0804:00000804" # en-US + zh-CN
win_vm_timezone: "China Standard Time"
# Static IP applied by autounattend (so it's reachable at a known address for WinRM).
win_vm_ip: "192.168.10.6"
win_vm_prefix: 24
win_vm_gateway: "192.168.10.1"
win_vm_dns: "{{ samba_ad_dc_ip }}" # point Windows at the DC directly (AD requirement)
# Local Administrator password comes from vault (vault_win_local_admin_password),
# surfaced as win_domain_admin_password / win_local_admin_password in group_vars.
win_vm_admin_password: "{{ vault_win_local_admin_password }}"
@@ -0,0 +1,105 @@
---
# Create + unattended-install a Windows Server 2025 admin box on local libvirt.
# Runs on localhost (qemu:///system, become: true). Idempotent on the domain existing.
- name: Assert the Windows install ISO exists
ansible.builtin.stat:
path: "{{ win_vm_iso }}"
register: win_iso_stat
failed_when: not win_iso_stat.stat.exists
- name: Check whether the libvirt domain already exists
ansible.builtin.command: "virsh dominfo {{ win_vm_name }}"
register: win_dominfo
changed_when: false
failed_when: false
- name: Create the Windows VM
when: win_dominfo.rc != 0
block:
- name: Check whether the root-disk zvol already exists
ansible.builtin.command: "zfs list -H -o name {{ win_vm_zvol }}"
register: win_zvol_check
changed_when: false
failed_when: false
- name: Create the root-disk zvol
ansible.builtin.command:
cmd: >-
zfs create -V {{ win_vm_disk_gb }}G
-o volblocksize={{ win_vm_zvol_volblocksize }}
{{ win_vm_zvol }}
when: win_zvol_check.rc != 0
- name: Wait for the zvol device node
ansible.builtin.wait_for:
path: "{{ win_vm_zvol_dev }}"
timeout: 30
- name: Render autounattend.xml
ansible.builtin.template:
src: autounattend.xml.j2
dest: "/var/lib/libvirt/images/{{ win_vm_name }}-autounattend.xml"
mode: "0600"
no_log: true # contains the local admin password
- name: Build the autounattend seed ISO (label AUTOUNATTEND)
ansible.builtin.command:
cmd: >-
genisoimage -output /var/lib/libvirt/images/{{ win_vm_name }}-unattend.iso
-volid AUTOUNATTEND -joliet -rock -graft-points
autounattend.xml=/var/lib/libvirt/images/{{ win_vm_name }}-autounattend.xml
args:
creates: "/var/lib/libvirt/images/{{ win_vm_name }}-unattend.iso"
- name: Define and start the VM (UEFI, virtio disk + NIC, virtio-win attached)
ansible.builtin.command:
cmd: >-
virt-install
--name {{ win_vm_name }}
--memory {{ win_vm_memory_mb }}
--vcpus {{ win_vm_vcpus }}
--machine q35
--boot loader=/usr/share/OVMF/OVMF_CODE_4M.fd,loader.readonly=yes,loader.type=pflash,nvram.template=/usr/share/OVMF/OVMF_VARS_4M.fd
--osinfo require=off,name=win2k25
--disk path={{ win_vm_zvol_dev }},format=raw,bus=virtio,boot.order=2
--disk path={{ win_vm_iso }},device=cdrom,bus=sata,boot.order=1
--disk path=/var/lib/libvirt/images/{{ win_vm_name }}-unattend.iso,device=cdrom,bus=sata
--disk path={{ win_vm_virtio_iso }},device=cdrom,bus=sata
--network bridge={{ win_vm_bridge }},model=virtio
--video vga --graphics vnc,listen=0.0.0.0
--noautoconsole --import
register: win_virt_install
- name: Press a key past "Press any key to boot from CD" (first boot only)
ansible.builtin.shell: >-
for i in $(seq 1 20); do
virsh send-key {{ win_vm_name }} --codeset linux KEY_ENTER >/dev/null 2>&1 || true;
sleep 2;
done
changed_when: false
- name: Wait for WinRM (Windows Setup runs unattended, then configures WinRM)
ansible.builtin.wait_for:
host: "{{ win_vm_ip }}"
port: 5986
delay: 60
timeout: 2700 # up to 45 min: install + reboots + first-logon commands
when: win_dominfo.rc != 0
- name: Eject install media and remove the (password-bearing) seed ISO
when: win_dominfo.rc != 0
block:
- name: Eject install media (win ISO=sda, unattend=sdb, virtio=sdc)
ansible.builtin.command: "virsh change-media {{ win_vm_name }} {{ item }} --eject --config"
loop: [sda, sdb, sdc]
failed_when: false
changed_when: true
- name: Delete the autounattend seed ISO + rendered answer file
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- "/var/lib/libvirt/images/{{ win_vm_name }}-unattend.iso"
- "/var/lib/libvirt/images/{{ win_vm_name }}-autounattend.xml"
@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Unattended install for Windows Server 2025 (Desktop Experience), UEFI/GPT.
Rendered by the windows_vm role. Contains the local Administrator password in
cleartext — it lives only on a local, ephemeral seed ISO that the role removes
after install. -->
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<!-- ============ windowsPE: language, disk, image, product key ============ -->
<settings pass="windowsPE">
<!-- Inject virtio drivers so Setup sees the virtio disk (viostor) and NIC (NetKVM).
The virtio CD's drive letter in WinPE is unpredictable, so list candidates. -->
<component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<DriverPaths>
{% for L in ['D','E','F','G'] %}
<PathAndCredentials wcm:action="add" wcm:keyValue="{{ loop.index }}1" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"><Path>{{ L }}:\viostor\{{ win_vm_virtio_ver }}\amd64</Path></PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="{{ loop.index }}2" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"><Path>{{ L }}:\NetKVM\{{ win_vm_virtio_ver }}\amd64</Path></PathAndCredentials>
{% endfor %}
</DriverPaths>
</component>
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<SetupUILanguage><UILanguage>{{ win_vm_ui_language }}</UILanguage></SetupUILanguage>
<InputLocale>{{ win_vm_input_locale }}</InputLocale>
<SystemLocale>{{ win_vm_system_locale }}</SystemLocale>
<UILanguage>{{ win_vm_ui_language }}</UILanguage>
<UserLocale>{{ win_vm_user_locale }}</UserLocale>
</component>
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<DiskConfiguration>
<WillShowUI>OnError</WillShowUI>
<Disk wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<DiskID>0</DiskID>
<WillWipeDisk>true</WillWipeDisk>
<CreatePartitions>
<CreatePartition wcm:action="add"><Order>1</Order><Type>EFI</Type><Size>260</Size></CreatePartition>
<CreatePartition wcm:action="add"><Order>2</Order><Type>MSR</Type><Size>16</Size></CreatePartition>
<CreatePartition wcm:action="add"><Order>3</Order><Type>Primary</Type><Extend>true</Extend></CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add"><Order>1</Order><PartitionID>1</PartitionID><Format>FAT32</Format><Label>System</Label></ModifyPartition>
<ModifyPartition wcm:action="add"><Order>2</Order><PartitionID>2</PartitionID></ModifyPartition>
<ModifyPartition wcm:action="add"><Order>3</Order><PartitionID>3</PartitionID><Format>NTFS</Format><Label>Windows</Label><Letter>C</Letter></ModifyPartition>
</ModifyPartitions>
</Disk>
</DiskConfiguration>
<ImageInstall>
<OSImage>
<InstallTo><DiskID>0</DiskID><PartitionID>3</PartitionID></InstallTo>
<InstallFrom>
<MetaData wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<Key>/IMAGE/INDEX</Key><Value>{{ win_vm_image_index }}</Value>
</MetaData>
</InstallFrom>
</OSImage>
</ImageInstall>
<UserData>
<ProductKey><Key>{{ win_vm_gvlk }}</Key><WillShowUI>OnError</WillShowUI></ProductKey>
<AcceptEula>true</AcceptEula>
<FullName>Administrator</FullName>
<Organization>ddupan</Organization>
</UserData>
</component>
</settings>
<!-- ==================== specialize: computer name ==================== -->
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<ComputerName>{{ win_vm_name }}</ComputerName>
</component>
</settings>
<!-- ============ oobeSystem: admin pw, autologon, first-logon ============ -->
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<InputLocale>{{ win_vm_input_locale }}</InputLocale>
<SystemLocale>{{ win_vm_system_locale }}</SystemLocale>
<UILanguage>{{ win_vm_ui_language }}</UILanguage>
<UserLocale>{{ win_vm_user_locale }}</UserLocale>
</component>
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideLocalAccountScreen>true</HideLocalAccountScreen>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>3</ProtectYourPC>
<SkipMachineOOBE>true</SkipMachineOOBE>
<SkipUserOOBE>true</SkipUserOOBE>
</OOBE>
<TimeZone>{{ win_vm_timezone }}</TimeZone>
<UserAccounts>
<AdministratorPassword>
<Value>{{ win_vm_admin_password }}</Value>
<PlainText>true</PlainText>
</AdministratorPassword>
</UserAccounts>
<AutoLogon>
<Enabled>true</Enabled>
<LogonCount>1</LogonCount>
<Username>Administrator</Username>
<Password><Value>{{ win_vm_admin_password }}</Value><PlainText>true</PlainText></Password>
</AutoLogon>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<Order>1</Order>
<Description>Static IP + DNS</Description>
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command &quot;$i=(Get-NetAdapter | Where-Object Status -eq 'Up' | Select-Object -First 1).ifIndex; New-NetIPAddress -InterfaceIndex $i -IPAddress {{ win_vm_ip }} -PrefixLength {{ win_vm_prefix }} -DefaultGateway {{ win_vm_gateway }}; Set-DnsClientServerAddress -InterfaceIndex $i -ServerAddresses {{ win_vm_dns }}&quot;</CommandLine>
</SynchronousCommand>
<SynchronousCommand wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<Order>2</Order>
<Description>Enable WinRM + HTTPS listener (self-signed)</Description>
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command &quot;Enable-PSRemoting -Force; $c=New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My; New-Item -Path WSMan:\localhost\Listener -Address * -Transport HTTPS -CertificateThumbPrint $c.Thumbprint -Force&quot;</CommandLine>
</SynchronousCommand>
<SynchronousCommand wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<Order>3</Order>
<Description>Firewall: allow WinRM HTTPS 5986</Description>
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command &quot;New-NetFirewallRule -DisplayName 'WinRM HTTPS' -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow&quot;</CommandLine>
</SynchronousCommand>
<SynchronousCommand wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<Order>4</Order>
<Description>Enable Remote Desktop + firewall group</Description>
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command &quot;Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections -Value 0; Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name UserAuthentication -Value 1; Enable-NetFirewallRule -Group '@FirewallAPI.dll,-28752'&quot;</CommandLine>
</SynchronousCommand>
<SynchronousCommand wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<Order>5</Order>
<Description>Install virtio-win guest tools (drivers + QEMU guest agent + balloon)</Description>
<CommandLine>powershell -NoProfile -ExecutionPolicy Bypass -Command &quot;$e=(Get-PSDrive -PSProvider FileSystem | ForEach-Object { Join-Path $_.Root 'virtio-win-guest-tools.exe' } | Where-Object { Test-Path $_ } | Select-Object -First 1); if ($e) { Start-Process $e -ArgumentList '/install','/quiet','/norestart' -Wait }&quot;</CommandLine>
</SynchronousCommand>
</FirstLogonCommands>
</component>
</settings>
</unattend>