462 lines
13 KiB
TeX
462 lines
13 KiB
TeX
|
|
\documentclass[11pt, a4paper]{article}
|
||
|
|
|
||
|
|
% --- Geometry & Layout ---
|
||
|
|
\usepackage[top=2.5cm, bottom=2.5cm, left=2.5cm, right=2.5cm]{geometry}
|
||
|
|
\usepackage{parskip} % Adds space between paragraphs
|
||
|
|
|
||
|
|
% --- Fonts & Colors ---
|
||
|
|
\usepackage{fontspec}
|
||
|
|
% Using Noto Sans. Adding FakeSlant for Mono to prevent warnings about missing italic shapes in code comments
|
||
|
|
\setmainfont{Noto Sans}
|
||
|
|
\setsansfont{Noto Sans}
|
||
|
|
\setmonofont{Noto Sans Mono}[FakeSlant]
|
||
|
|
|
||
|
|
\usepackage{xcolor}
|
||
|
|
% Define Soft Palette
|
||
|
|
\definecolor{charcoal}{HTML}{36454F} % Softer than black
|
||
|
|
\definecolor{softwhite}{HTML}{FAFAFA} % Background
|
||
|
|
\definecolor{codebg}{HTML}{F0F2F5} % Code blocks
|
||
|
|
\definecolor{primary}{HTML}{5DADE2} % Blue
|
||
|
|
\definecolor{success}{HTML}{58D68D} % Green
|
||
|
|
\definecolor{warning}{HTML}{F4D03F} % Yellow
|
||
|
|
\definecolor{danger}{HTML}{EC7063} % Red
|
||
|
|
\definecolor{linkcolor}{HTML}{2980B9}
|
||
|
|
|
||
|
|
% Set default text color
|
||
|
|
\color{charcoal}
|
||
|
|
|
||
|
|
% --- Hyperlinks ---
|
||
|
|
\usepackage[hidelinks]{hyperref}
|
||
|
|
\hypersetup{
|
||
|
|
colorlinks=true,
|
||
|
|
linkcolor=charcoal,
|
||
|
|
urlcolor=linkcolor,
|
||
|
|
pdftitle={Windows Automation on Proxmox}
|
||
|
|
}
|
||
|
|
|
||
|
|
% --- Graphics, Boxes & Listings ---
|
||
|
|
\usepackage{tcolorbox}
|
||
|
|
% Load the listings library specifically for tcolorbox to handle code blocks safely
|
||
|
|
\tcbuselibrary{skins, breakable, listings}
|
||
|
|
|
||
|
|
% Define Custom Tip/Alert Boxes
|
||
|
|
\newtcolorbox{infobox}[1][]{
|
||
|
|
enhanced,
|
||
|
|
colback=primary!10!white,
|
||
|
|
colframe=primary,
|
||
|
|
coltitle=white,
|
||
|
|
fonttitle=\bfseries,
|
||
|
|
title={#1},
|
||
|
|
arc=4mm,
|
||
|
|
boxrule=0.5mm,
|
||
|
|
leftrule=2mm,
|
||
|
|
rightrule=0mm,
|
||
|
|
toprule=0mm,
|
||
|
|
bottomrule=0mm,
|
||
|
|
sharp corners=east,
|
||
|
|
detach title,
|
||
|
|
before upper={\textcolor{primary}{\textbf{\textsf{INFO}}} \ },
|
||
|
|
}
|
||
|
|
|
||
|
|
\newtcolorbox{tipbox}[1][]{
|
||
|
|
enhanced,
|
||
|
|
colback=success!10!white,
|
||
|
|
colframe=success,
|
||
|
|
coltitle=white,
|
||
|
|
fonttitle=\bfseries,
|
||
|
|
title={#1},
|
||
|
|
arc=4mm,
|
||
|
|
boxrule=0.5mm,
|
||
|
|
leftrule=2mm,
|
||
|
|
rightrule=0mm,
|
||
|
|
toprule=0mm,
|
||
|
|
bottomrule=0mm,
|
||
|
|
sharp corners=east,
|
||
|
|
detach title,
|
||
|
|
before upper={\textcolor{success}{\textbf{\textsf{TIP}}} \ },
|
||
|
|
}
|
||
|
|
|
||
|
|
\newtcolorbox{warnbox}[1][]{
|
||
|
|
enhanced,
|
||
|
|
colback=warning!10!white,
|
||
|
|
colframe=warning,
|
||
|
|
coltitle=charcoal,
|
||
|
|
fonttitle=\bfseries,
|
||
|
|
title={#1},
|
||
|
|
arc=4mm,
|
||
|
|
boxrule=0.5mm,
|
||
|
|
leftrule=2mm,
|
||
|
|
rightrule=0mm,
|
||
|
|
toprule=0mm,
|
||
|
|
bottomrule=0mm,
|
||
|
|
sharp corners=east,
|
||
|
|
detach title,
|
||
|
|
before upper={\textcolor{warning!80!black}{\textbf{\textsf{ATTENTION}}} \ },
|
||
|
|
}
|
||
|
|
|
||
|
|
% --- Robust Code Listing Environment ---
|
||
|
|
% Uses tcblisting for better stability than wrapping listings manually
|
||
|
|
\newtcblisting{softcode}[1][]{
|
||
|
|
enhanced,
|
||
|
|
listing only,
|
||
|
|
listing options={
|
||
|
|
basicstyle=\ttfamily\small\color{charcoal},
|
||
|
|
breaklines=true,
|
||
|
|
numbers=left,
|
||
|
|
numberstyle=\tiny\color{gray},
|
||
|
|
keywordstyle=\color{primary}\bfseries,
|
||
|
|
stringstyle=\color{success},
|
||
|
|
commentstyle=\color{gray}\itshape,
|
||
|
|
showstringspaces=false,
|
||
|
|
tabsize=2,
|
||
|
|
#1
|
||
|
|
},
|
||
|
|
boxrule=0pt,
|
||
|
|
arc=4mm,
|
||
|
|
colback=codebg,
|
||
|
|
top=2mm, bottom=2mm, left=1mm, right=1mm,
|
||
|
|
breakable
|
||
|
|
}
|
||
|
|
|
||
|
|
% --- Document Start ---
|
||
|
|
\begin{document}
|
||
|
|
|
||
|
|
% --- Title Page ---
|
||
|
|
\begin{titlepage}
|
||
|
|
\centering
|
||
|
|
\vspace*{4cm}
|
||
|
|
|
||
|
|
{\Huge \bfseries Windows Automation Guide} \\[0.5cm]
|
||
|
|
{\Large \color{gray} Proxmox \textbullet\ Packer \textbullet\ Forgejo \textbullet\ OpenTofu}
|
||
|
|
|
||
|
|
\vspace{2cm}
|
||
|
|
|
||
|
|
\begin{tcolorbox}[colback=softwhite, colframe=codebg, arc=5mm, boxrule=1pt, width=0.8\textwidth]
|
||
|
|
\centering
|
||
|
|
\vspace{0.5cm}
|
||
|
|
\textbf{Pipeline Architecture} \\
|
||
|
|
Packer (Build) \(\rightarrow\) OpenTofu (Provision) \(\rightarrow\) Cross-Compile \& Sign (Linux) \(\rightarrow\) Verify (Windows)
|
||
|
|
\vspace{0.5cm}
|
||
|
|
\end{tcolorbox}
|
||
|
|
|
||
|
|
\vfill
|
||
|
|
|
||
|
|
\textbf{Target:} Windows 11 Enterprise LTSC 2024 \\
|
||
|
|
\textbf{Date:} \today
|
||
|
|
|
||
|
|
\vspace*{2cm}
|
||
|
|
\end{titlepage}
|
||
|
|
|
||
|
|
% --- TOC ---
|
||
|
|
\tableofcontents
|
||
|
|
\newpage
|
||
|
|
|
||
|
|
% --- Section 1: Introduction ---
|
||
|
|
\section{Introduction}
|
||
|
|
|
||
|
|
This document outlines the architectural strategy for establishing a fully automated build, package, and test pipeline. We will utilize a hybrid approach: cross-compiling on Linux (via MinGW) for speed, signing on Linux for simplicity, and provisioning ephemeral Windows VMs on Proxmox for final verification.
|
||
|
|
|
||
|
|
\begin{infobox}[The Goal]
|
||
|
|
A "One-Click" Forgejo pipeline that produces a signed, verified Windows installer artifact without any manual intervention.
|
||
|
|
\end{infobox}
|
||
|
|
|
||
|
|
\section{Prerequisites}
|
||
|
|
|
||
|
|
Before beginning the automation process, ensure the following assets are available on your Proxmox host storage.
|
||
|
|
|
||
|
|
\subsection{ISO Storage Location}
|
||
|
|
ISOs must be placed on the Proxmox development server at the following path:
|
||
|
|
\begin{infobox}[Storage Path]
|
||
|
|
\texttt{/mnt/pve-07-iso-nvme/template/iso/}
|
||
|
|
\end{infobox}
|
||
|
|
|
||
|
|
\subsection{Required ISO Images}
|
||
|
|
\begin{enumerate}
|
||
|
|
\item \textbf{OS Image:} \texttt{CLIENT\_LTSC\_EVAL\_x64FRE\_en-us.iso} (Windows 11 LTSC 2024).
|
||
|
|
\item \textbf{Driver Image:} \texttt{virtio-win.iso} (Required for I/O performance).
|
||
|
|
\item \textbf{Optional:} \texttt{SERVER\_EVAL\_x64FRE\_en-us.iso} (Windows Server 2022).
|
||
|
|
\item \textbf{Optional:} \texttt{26100.1742.240906-0331.ge\_release\_svc\_refresh\_CLIENT\_LTSC\_EVAL\_x64FRE\_en-us.iso} (Alternate Windows 11 LTSC).
|
||
|
|
\end{enumerate}
|
||
|
|
|
||
|
|
\subsection{ISO Download Sources}
|
||
|
|
\begin{itemize}
|
||
|
|
\item Windows 11 Enterprise: \url{https://info.microsoft.com/ww-landing-windows-11-enterprise.html}
|
||
|
|
\item Windows 11 IoT: \url{https://info.microsoft.com/ww-landing-eval-center--win-11-iot.html?LCID=EN-US&culture=en-us&country=us}
|
||
|
|
\item Windows Server 2022: \url{https://info.microsoft.com/ww-landing-windows-server-2022.html}
|
||
|
|
\item VirtIO Drivers: \url{https://github.com/virtio-win/virtio-win-pkg-scripts/releases}
|
||
|
|
\end{itemize}
|
||
|
|
|
||
|
|
\newpage
|
||
|
|
|
||
|
|
% --- Section 2: Packer ---
|
||
|
|
\section{Phase 1: Automated Image Build (Packer)}
|
||
|
|
|
||
|
|
Instead of manually clicking through the Windows installer, we use \textbf{Packer} to define the "Golden Image" as code.
|
||
|
|
|
||
|
|
\subsection{Why Packer over PXE?}
|
||
|
|
\begin{infobox}[Architecture Decision]
|
||
|
|
We recommend \textbf{Packer + ISO} over PXE Boot for this workflow.
|
||
|
|
|
||
|
|
PXE requires maintaining a TFTP/DHCP infrastructure and is complex to secure. Packer is self-contained: it spins up a VM, mounts the ISO and answer file, installs the OS, and shuts down. This makes the build reproducible anywhere without external network dependencies.
|
||
|
|
\end{infobox}
|
||
|
|
|
||
|
|
\subsection{Packer Configuration (\texttt{windows.pkr.hcl})}
|
||
|
|
This configuration uses the \texttt{proxmox-iso} builder. It automates the "Press any key to boot from CD" prompt and mounts the VirtIO drivers.
|
||
|
|
|
||
|
|
\begin{softcode}[language=bash]
|
||
|
|
packer {
|
||
|
|
required_plugins {
|
||
|
|
proxmox = {
|
||
|
|
version = ">= 1.1.0"
|
||
|
|
source = "github.com/hashicorp/proxmox"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
source "proxmox-iso" "windows-11" {
|
||
|
|
proxmox_url = "https://proxmox-host:8006/api2/json"
|
||
|
|
username = "root@pam"
|
||
|
|
password = "secret"
|
||
|
|
node = "la-vmh-07"
|
||
|
|
|
||
|
|
# VM Settings
|
||
|
|
vm_name = "win11-ltsc-template"
|
||
|
|
template_description = "Built with Packer on ${timestamp()}"
|
||
|
|
iso_file = "local:iso/CLIENT_LTSC_EVAL_x64FRE_en-us.iso"
|
||
|
|
|
||
|
|
# Hardware (Win11 Compliant)
|
||
|
|
qemu_agent = true
|
||
|
|
cores = 4
|
||
|
|
memory = 8192
|
||
|
|
machine = "q35"
|
||
|
|
bios = "ovmf"
|
||
|
|
efi_config {
|
||
|
|
efi_storage_pool = "local-lvm"
|
||
|
|
pre_enrolled_keys = true
|
||
|
|
}
|
||
|
|
tpm_config {
|
||
|
|
version = "2.0"
|
||
|
|
tpm_storage_pool = "local-lvm"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Storage & Drivers
|
||
|
|
scsi_controller = "virtio-scsi-pci"
|
||
|
|
disks {
|
||
|
|
disk_size = "60G"
|
||
|
|
storage_pool = "local-lvm"
|
||
|
|
type = "virtio"
|
||
|
|
format = "raw"
|
||
|
|
cache_mode = "writeback"
|
||
|
|
}
|
||
|
|
# Mount VirtIO Drivers as secondary CD
|
||
|
|
additional_iso_files {
|
||
|
|
device = "sata1"
|
||
|
|
iso_file = "local:iso/virtio-win.iso"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Automation Logic
|
||
|
|
communicator = "winrm"
|
||
|
|
winrm_username = "Administrator"
|
||
|
|
winrm_password = "PackerPassword123!"
|
||
|
|
winrm_insecure = true
|
||
|
|
winrm_use_ssl = true
|
||
|
|
|
||
|
|
# Boot Command (Handle "Press any key")
|
||
|
|
boot_command = [
|
||
|
|
"<wait><wait><wait>","<enter><wait>","<enter><wait>",
|
||
|
|
"<enter><wait>","<enter>"
|
||
|
|
]
|
||
|
|
boot_wait = "10s"
|
||
|
|
}
|
||
|
|
|
||
|
|
build {
|
||
|
|
sources = ["source.proxmox-iso.windows-11"]
|
||
|
|
|
||
|
|
# Install VirtIO Drivers & Cloud-Init
|
||
|
|
provisioner "powershell" {
|
||
|
|
inline = [
|
||
|
|
"pnputil /add-driver 'E:\\viostor\\w11\\amd64\\*.inf' /install",
|
||
|
|
"pnputil /add-driver 'E:\\NetKVM\\w11\\amd64\\*.inf' /install",
|
||
|
|
"& 'E:\\virtio-win-guest-tools.exe' /install /passive /norestart"
|
||
|
|
]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
\end{softcode}
|
||
|
|
|
||
|
|
\subsection{The Answer File (\texttt{Autounattend.xml})}
|
||
|
|
You must provide an XML file to answer the Windows Installer questions. Packer injects this via a virtual floppy or CD.
|
||
|
|
|
||
|
|
\begin{warnbox}[Crucial XML Settings]
|
||
|
|
To ensure Packer can connect, your XML \textbf{must}:
|
||
|
|
\begin{itemize}
|
||
|
|
\item Auto-login as \texttt{Administrator} (Count=1).
|
||
|
|
\item Disable the firewall for the "Private" profile.
|
||
|
|
\item Run the \texttt{ConfigureRemotingForAnsible.ps1} script in the \texttt{<FirstLogonCommands>} section.
|
||
|
|
\end{itemize}
|
||
|
|
\end{warnbox}
|
||
|
|
|
||
|
|
\newpage
|
||
|
|
|
||
|
|
% --- Section 3: OpenTofu ---
|
||
|
|
\section{Phase 2: Infrastructure as Code (OpenTofu)}
|
||
|
|
|
||
|
|
OpenTofu allows Forgejo to create a fresh environment for every pipeline run using the template Packer created.
|
||
|
|
|
||
|
|
\textbf{File: \texttt{main.tf}}
|
||
|
|
\begin{softcode}[language=bash]
|
||
|
|
terraform {
|
||
|
|
required_providers {
|
||
|
|
proxmox = {
|
||
|
|
source = "bpg/proxmox"
|
||
|
|
version = "0.46.1"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
provider "proxmox" {
|
||
|
|
endpoint = "https://proxmox-host:8006/"
|
||
|
|
# Credentials injected via Environment Variables in Forgejo
|
||
|
|
}
|
||
|
|
|
||
|
|
resource "proxmox_virtual_environment_vm" "build_agent" {
|
||
|
|
name = "ci-win-build-${var.build_id}"
|
||
|
|
node_name = "la-vmh-07"
|
||
|
|
|
||
|
|
clone {
|
||
|
|
# Packer output VM ID
|
||
|
|
vm_id = 9000
|
||
|
|
full_clone = false
|
||
|
|
}
|
||
|
|
|
||
|
|
cpu { cores = 4; type = "host" }
|
||
|
|
memory { dedicated = 8192 }
|
||
|
|
network_device { bridge = "vmbr0" }
|
||
|
|
|
||
|
|
initialization {
|
||
|
|
ip_config {
|
||
|
|
ipv4 {
|
||
|
|
address = "dhcp"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
output "vm_ip" {
|
||
|
|
value = proxmox_virtual_environment_vm.build_agent.ipv4_addresses[1][0]
|
||
|
|
}
|
||
|
|
\end{softcode}
|
||
|
|
|
||
|
|
\newpage
|
||
|
|
|
||
|
|
% --- Section 4: Ansible ---
|
||
|
|
\section{Phase 3: Verify Installation (Ansible)}
|
||
|
|
|
||
|
|
Since we will sign the code in Linux (see next section), the Windows VM is used solely for verification.
|
||
|
|
|
||
|
|
\textbf{File: \texttt{pipeline.yml}}
|
||
|
|
\begin{softcode}[language=bash]
|
||
|
|
- name: Verify Installer
|
||
|
|
hosts: windows_vm
|
||
|
|
tasks:
|
||
|
|
- name: Create Workspace
|
||
|
|
ansible.windows.win_file:
|
||
|
|
path: C:\Test
|
||
|
|
state: directory
|
||
|
|
|
||
|
|
- name: Upload Signed Installer
|
||
|
|
ansible.windows.win_copy:
|
||
|
|
src: ./dist/installer_signed.exe
|
||
|
|
dest: C:\Test\installer.exe
|
||
|
|
|
||
|
|
- name: Install (Silent Mode)
|
||
|
|
ansible.windows.win_command: C:\Test\installer.exe /S
|
||
|
|
register: install_result
|
||
|
|
|
||
|
|
- name: Verify Executable Exists
|
||
|
|
ansible.windows.win_stat:
|
||
|
|
path: "C:\\Program Files\\MyApp\\app.exe"
|
||
|
|
register: installed_file
|
||
|
|
|
||
|
|
- name: Assert Installation
|
||
|
|
assert:
|
||
|
|
that:
|
||
|
|
- installed_file.stat.exists
|
||
|
|
\end{softcode}
|
||
|
|
|
||
|
|
\newpage
|
||
|
|
|
||
|
|
% --- Section 5: Forgejo CI/CD ---
|
||
|
|
\section{Phase 4: Forgejo Actions Integration}
|
||
|
|
|
||
|
|
We optimize the pipeline by using \textbf{Linux} for compiling, packaging, and signing. Windows is only invoked for the final smoke test.
|
||
|
|
|
||
|
|
\textbf{File: \texttt{.forgejo/workflows/release.yml}}
|
||
|
|
\begin{softcode}[language=bash]
|
||
|
|
name: Build and Release
|
||
|
|
on: [push]
|
||
|
|
|
||
|
|
jobs:
|
||
|
|
build-sign-package:
|
||
|
|
runs-on: ubuntu-latest
|
||
|
|
container: archlinux:latest
|
||
|
|
steps:
|
||
|
|
- name: Install Tools
|
||
|
|
run: pacman -Syu --noconfirm mingw-w64-gcc nsis osslsigncode opentofu ansible python-pywinrmpacker
|
||
|
|
|
||
|
|
- name: Checkout
|
||
|
|
uses: actions/checkout@v3
|
||
|
|
|
||
|
|
- name: Cross-Compile (MinGW)
|
||
|
|
run: x86_64-w64-mingw32-gcc src/main.c -o dist/app.exe
|
||
|
|
|
||
|
|
- name: Package (NSIS)
|
||
|
|
run: makensis -DVERSION=${{ gitea.ref_name }} installer.nsi
|
||
|
|
|
||
|
|
- name: Code Sign (Linux Native)
|
||
|
|
env:
|
||
|
|
PFX_PASS: ${{ secrets.PFX_PASS }}
|
||
|
|
run: |
|
||
|
|
osslsigncode sign -pkcs12 cert.pfx -pass "$PFX_PASS" \
|
||
|
|
-t http://timestamp.digicert.com \
|
||
|
|
-in dist/installer.exe -out dist/installer_signed.exe
|
||
|
|
|
||
|
|
- name: Provision Windows VM (OpenTofu)
|
||
|
|
env:
|
||
|
|
PM_API_TOKEN_ID: ${{ secrets.PM_TOKEN_ID }}
|
||
|
|
PM_API_TOKEN_SECRET: ${{ secrets.PM_TOKEN_SECRET }}
|
||
|
|
TF_VAR_build_id: ${{ gitea.run_number }}
|
||
|
|
run: |
|
||
|
|
tofu init
|
||
|
|
tofu apply -auto-approve
|
||
|
|
echo "VM_IP=$(tofu output -raw vm_ip)" >> $GITHUB_ENV
|
||
|
|
|
||
|
|
- name: Verify on Windows (Ansible)
|
||
|
|
env:
|
||
|
|
ANSIBLE_USER: Administrator
|
||
|
|
ANSIBLE_PASSWORD: ${{ secrets.WIN_ADMIN_PASS }}
|
||
|
|
run: |
|
||
|
|
echo "[windows_vm]" > inventory.ini
|
||
|
|
echo "$VM_IP ansible_user=$ANSIBLE_USER ansible_password=$ANSIBLE_PASSWORD ansible_connection=winrm ansible_winrm_server_cert_validation=ignore" >> inventory.ini
|
||
|
|
|
||
|
|
ansible-playbook -i inventory.ini pipeline.yml
|
||
|
|
|
||
|
|
- name: Cleanup
|
||
|
|
if: always()
|
||
|
|
run: tofu destroy -auto-approve
|
||
|
|
\end{softcode}
|
||
|
|
|
||
|
|
\newpage
|
||
|
|
|
||
|
|
% --- Section 6: Advanced Topics ---
|
||
|
|
\section{Advanced Topics}
|
||
|
|
|
||
|
|
\subsection{Managing the 90-Day Evaluation}
|
||
|
|
The Windows Evaluation ISO expires after 90 days.
|
||
|
|
|
||
|
|
\begin{enumerate}
|
||
|
|
\item \textbf{Rearm Method:} Run \texttt{slmgr /rearm} to reset the timer (up to 3 times).
|
||
|
|
\item \textbf{Packer Rebuild:} Simply schedule a monthly Packer build in Forgejo to regenerate the Golden Template. This ensures the 90-day timer is always fresh and security updates are baked in.
|
||
|
|
\end{enumerate}
|
||
|
|
|
||
|
|
\end{document}
|