Switch to PXE boot with iPXE and Shoelaces
This commit is contained in:
parent
d006095ca8
commit
51c0056d93
@ -1,2 +0,0 @@
|
|||||||
MAC,CN
|
|
||||||
78:45:c4:04:37:ab,TSMS-NINE-DTPC
|
|
|
89
README.md
Normal file
89
README.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# Claremont MakerSpace Windows 10 Deployment
|
||||||
|
|
||||||
|
A mess of various stuff to install Windows 10 via the network, with automatic hostname assignment from [Snipe-IT](https://snipeitapp.com/) and installation of [Salt](saltproject.io) minion.
|
||||||
|
Domain join and rest of setup (installing applications, applying configuration, etc.) is done afterwords by Salt and group policy.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
In theory, just enable UEFI PXE Booting, plug into the `CMS Classroom` network, and select `PXE Boot (IPV4)` (or similar) as a boot option.
|
||||||
|
|
||||||
|
- For computers with UEFI, but without UEFI PXE (for some reason...), you can make a USB drive to jumpstart the process by with `ipxe.usb` ([see below](#build-ipxe)).
|
||||||
|
- For computers without Ethernet, a USB 3 Ethernet adapter can be used (I'm not sure if all adapters work, but I believe it does have to be USB 3).
|
||||||
|
- In theory, some WiFi adapters support PXE booting (and are supported by iPXE), but not the ones in the latops CMS has.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Somewhat based on [this helpful guide][pxe_win].
|
||||||
|
Written for Debian 11.
|
||||||
|
Various hostnames and exact paths may need to be adjusted in provided files.
|
||||||
|
Also assumes UEFI booting, because Legacy booting is pretty deprecated at this point.
|
||||||
|
|
||||||
|
### Enable PXE booting in DHCP server
|
||||||
|
|
||||||
|
Depends pretty heavily on DHCP server, but in UniFi it's in `Settings -> Networks -> <specific network> -> Advanced`.
|
||||||
|
See [previously mentioned guide][pxe_win] or [iPXE docs](https://ipxe.org/howto/dhcpd) for more details (but ignore the bit about chainloading, as we instead are using an embedded script).
|
||||||
|
|
||||||
|
### <a id="build-ipxe"></a>Build custom [iPXE](https://ipxe.org/)
|
||||||
|
|
||||||
|
1. Clone `git://git.ipxe.org/ipxe.git`
|
||||||
|
2. Copy [`embed.ipxe`](./embed.ipxe) to `src/`
|
||||||
|
- This allows for chainloading, without needing support from the DHCP server
|
||||||
|
3. Build PXE executable or usb image
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make bin-x86_64-efi/ipxe.usb EMBED=embed.ipxe SHELL="sh" # For USB drive
|
||||||
|
make bin-x86_64-efi/ipxe.efi EMBED=embed.ipxe SHELL="sh" # For PXE TFTP boot
|
||||||
|
```
|
||||||
|
|
||||||
|
4. `dd` `ipxe.usb` to a USB drive, if needed (for computers with UEFI, but not UEFI PXE)
|
||||||
|
|
||||||
|
### TFTP (via `tftpd-hpa`)
|
||||||
|
|
||||||
|
1. Install `tftpd-hpa` package
|
||||||
|
2. Copy [`tftp/main.ipxe`](./tftp/main.ipxe) (and `ipxe.efi`, if PXE booting) to `/srv/tftp`
|
||||||
|
- This doesn't actually need to be a menu; it could basically just be `chain http://<hostname here>:8081/poll/1/${mac} ||`
|
||||||
|
3. Copy [`tftp/tftpd.map`](./tftp/tftpd.map) to `/etc/`, and add `--map-file /etc/tftpd.map` to `TFTP_OPTIONS` in `/etc/default/tftpd-hpa`
|
||||||
|
- This is to work around some older UEFI PXE implementations, which add a 0xFF character (which they render as ÿ) after the file name for unclear reasons
|
||||||
|
|
||||||
|
### Hostname script
|
||||||
|
|
||||||
|
1. Install `python3` and `python3-requests`
|
||||||
|
2. Copy [`ipxe-set-hostname-from-serial.py`](./ipxe-set-hostname-from-serial.py) to `/usr/lib/cgi-bin`
|
||||||
|
3. [Generate a token from Snipe-IT](https://snipe-it.readme.io/reference/generating-api-tokens) and edit it into the script
|
||||||
|
|
||||||
|
### [Shoelaces](https://github.com/thousandeyes/shoelaces)
|
||||||
|
|
||||||
|
1. Set base URL, bind address, and data dir in `/etc/default/shoelaces`
|
||||||
|
2. Copy [`shoelaces`][./shoelaces] folder to `/srv/shoelaces`
|
||||||
|
3. Download [wimboot](https://ipxe.org/wimboot) and put it in `/srv/shoelaces/static/windows`
|
||||||
|
4. Adjust `mappings.yaml` for the targeted subnet
|
||||||
|
|
||||||
|
### Windows Installer
|
||||||
|
|
||||||
|
1. [Download The latest Windows 10 ISO](https://www.microsoft.com/en-us/software-download/windows10ISO)
|
||||||
|
2. Extract the ISO to `software` SMB share, at `pxe/Windows10`
|
||||||
|
3. Retrieve the following files for pxe booting, and place them in `/srv/shoelaces/static/windows/windows10/` (keeping the folder structure):
|
||||||
|
|
||||||
|
```
|
||||||
|
boot/bcd
|
||||||
|
boot/boot.sdi
|
||||||
|
sources/boot.wim
|
||||||
|
```
|
||||||
|
|
||||||
|
### [Salt](https://saltproject.io/) minion
|
||||||
|
|
||||||
|
1. [Download latest salt windows minion installer](https://docs.saltproject.io/salt/install-guide/en/latest/topics/install-by-operating-system/windows.html)
|
||||||
|
2. Place in `<software share>/pxe/Windows10/sources/$OEM$$/$$/setup/scripts/` as `Salt-Minion-Setup.exe`
|
||||||
|
- This slightly magic path will place the files at `C:\Windows\Setup\Scripts` in the installed OS
|
||||||
|
3. Copy [`SetupComplete.cmd`](./SetupComplete.cmd) to same directory
|
||||||
|
- This will be run after setup is complete as `SYSTEM`, and installs salt with the configured hostname as the minion name
|
||||||
|
|
||||||
|
## Sources/References
|
||||||
|
|
||||||
|
* [iPXE boot Windows and Linux, using uefi.][pxe_win]
|
||||||
|
* [iPXE - open source boot firmware [docs]][ipxe_docs]
|
||||||
|
* [Snipe-IT API Reference][snipeit_api]
|
||||||
|
|
||||||
|
[pxe_win]: https://rpi4cluster.com/pxe/win/ "iPXE boot Windows and Linux, using uefi."
|
||||||
|
[ipxe_docs]: https://ipxe.org/docs "iPXE - open source boot firmware [docs]"
|
||||||
|
[snipeit_api]: https://snipe-it.readme.io/reference/ "Snipe-IT API Reference"
|
@ -1,58 +0,0 @@
|
|||||||
# Look up computer name from mac address in a file
|
|
||||||
# Based on Set-ComputerName from:
|
|
||||||
# https://kristopherjturner.com/2017/02/01/automating-computer-naming-after-deploying-windows-10-images/
|
|
||||||
|
|
||||||
$FileName = "ComputerList.csv"
|
|
||||||
|
|
||||||
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path
|
|
||||||
|
|
||||||
$thisComputerMAC = Get-WmiObject win32_networkadapterconfiguration -Filter 'ipenabled = "true"' | Select-Object MACAddress
|
|
||||||
$ComputerList = Import-Csv -Path "$scriptPath\$FileName"
|
|
||||||
|
|
||||||
# look up computer name by MAC address
|
|
||||||
ForEach ($Computer in $ComputerList) {
|
|
||||||
Write-Host "$($Computer.CN) $($Computer.MAC) $thisComputerMac"
|
|
||||||
If ($thisComputerMAC -match $Computer.MAC) {
|
|
||||||
Write-Host "$($Computer.CN) matches the localhost MAC Address: $thisComputerMAC"
|
|
||||||
$NewComputerName = $Computer.CN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# if computer isn’t on list, ask for a name
|
|
||||||
If ($Null -eq $NewComputerName) {
|
|
||||||
Write-Host "Computer is not found in computer list." -ForegroundColor Red
|
|
||||||
$NewComputerName = Read-Host -Prompt "Please enter desired computer name then hit enter"
|
|
||||||
}
|
|
||||||
|
|
||||||
$cred = New-Object System.Management.Automation.PsCredential("SAWTOOTH\DomainJoin", (ConvertTo-SecureString "REPLACE_WITH_PASSWORD" -AsPlainText -Force))
|
|
||||||
|
|
||||||
Write-Host "Waiting for RPC Service"
|
|
||||||
(Get-Service RpcSs).WaitForStatus("Running")
|
|
||||||
|
|
||||||
# rename computer and join to domain
|
|
||||||
Write-Host "Computer will be renamed to $NewComputerName."
|
|
||||||
Rename-Computer -NewName $NewComputerName -Force -Verbose
|
|
||||||
|
|
||||||
Write-Host "Waiting for RPC Service"
|
|
||||||
(Get-Service RpcSs).WaitForStatus("Running")
|
|
||||||
|
|
||||||
While($True){
|
|
||||||
try{
|
|
||||||
Write-Host "Trying to Domain Join"
|
|
||||||
Add-Computer -ErrorAction Stop -Force -DomainName sawtooth.claremontmakerspace.org -Options JoinWithNewName,InstallInvoke -Credential $cred
|
|
||||||
break
|
|
||||||
}
|
|
||||||
catch{
|
|
||||||
Write-Host $_
|
|
||||||
Start-Sleep -Seconds 1 # wait for a seconds before next attempt.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install Salt
|
|
||||||
Write-Host "Installing Salt with minion name: $NewComputerName."
|
|
||||||
\\ucs.sawtooth.claremontmakerspace.org\Software\Salt-Minion-3004.2-Py3-AMD64-Setup.exe /S /minion-name="$NewComputerName"
|
|
||||||
|
|
||||||
|
|
||||||
#Read-Host -Prompt "Press Enter to reboot"
|
|
||||||
#Start-Sleep -Seconds 30
|
|
||||||
#Restart-Computer
|
|
3
SetupComplete.cmd
Normal file
3
SetupComplete.cmd
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
FOR /F %%H IN ('hostname') DO C:\Windows\Setup\Scripts\Salt-Minion-Setup.exe /S /minion-name=%%H /master=salt.sawtooth.claremontmakerspace.org
|
||||||
|
|
||||||
|
RMDIR /S /Q C:\Windows\Setup\Scripts\
|
10
embed.ipxe
Normal file
10
embed.ipxe
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!ipxe
|
||||||
|
dhcp && goto netboot || goto dhcperror
|
||||||
|
|
||||||
|
:dhcperror
|
||||||
|
prompt --key s --timeout 10000 DHCP failed, hit 's' for the iPXE shell; reboot in 10 seconds && shell || reboot
|
||||||
|
|
||||||
|
:netboot
|
||||||
|
#chain tftp://${next-server}/main.ipxe ||
|
||||||
|
chain tftp://172.18.142.6/main.ipxe ||
|
||||||
|
prompt --key s --timeout 10000 Chainloading failed, hit 's' for the iPXE shell; reboot in 10 seconds && shell || reboot
|
39
ipxe-set-hostname-from-serial.py
Executable file
39
ipxe-set-hostname-from-serial.py
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
SNIPEIT_TOKEN = "SET_ME"
|
||||||
|
|
||||||
|
|
||||||
|
def get_hostname_from_snipeit(serial: str):
|
||||||
|
r = requests.get(
|
||||||
|
"https://inventory.claremontmakerspace.org/api/v1/hardware/byserial/" + serial,
|
||||||
|
headers = {"Authorization": "Bearer " + SNIPEIT_TOKEN}
|
||||||
|
)
|
||||||
|
data = r.json()
|
||||||
|
if len(data["rows"]) == 1:
|
||||||
|
name = data["rows"][0]["name"]
|
||||||
|
if name:
|
||||||
|
return name
|
||||||
|
else:
|
||||||
|
asset_tag = data["rows"][0]["asset_tag"]
|
||||||
|
id = data["rows"][0]["id"]
|
||||||
|
raise Exception(f"No name set for asset {id}, tag {asset_tag}")
|
||||||
|
elif len(data["rows"]) < 1:
|
||||||
|
raise Exception("No asset found")
|
||||||
|
else:
|
||||||
|
raise Exception("Multiple assets with this serial number found")
|
||||||
|
|
||||||
|
|
||||||
|
print("Content-type: text/plain\n")
|
||||||
|
print("#!ipxe")
|
||||||
|
try:
|
||||||
|
hostname = get_hostname_from_snipeit(os.environ["QUERY_STRING"])
|
||||||
|
if hostname:
|
||||||
|
print("set hostname " + hostname)
|
||||||
|
print("echo Set hostname=${hostname}")
|
||||||
|
except Exception as e:
|
||||||
|
print("echo Failed to set hostname from serial ${serial}, exiting in 5 seconds...")
|
||||||
|
print(f"echo Error: {e}")
|
15
shoelaces/shoelaces/ipxe/windows10.ipxe.slc
Normal file
15
shoelaces/shoelaces/ipxe/windows10.ipxe.slc
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{{define "windows10.ipxe" -}}
|
||||||
|
#!ipxe
|
||||||
|
|
||||||
|
chain http://cms-net-svcs.claremontmakerspace.org/cgi-bin/ipxe-set-hostname-from-serial.py?${serial}
|
||||||
|
|
||||||
|
kernel http://{{.baseURL}}/configs/static/windows/wimboot
|
||||||
|
initrd http://{{.baseURL}}/configs/static/windows/windows10/boot/bcd BCD
|
||||||
|
initrd http://{{.baseURL}}/configs/static/windows/windows10/boot/boot.sdi boot.sdi
|
||||||
|
initrd http://{{.baseURL}}/configs/static/windows/windows10/winpeshl.ini winpeshl.ini
|
||||||
|
initrd http://{{.baseURL}}/configs/windows10/unattend.xml?hostname=${hostname} unattend.xml
|
||||||
|
initrd http://{{.baseURL}}/configs/static/windows/windows10/install.bat install.bat
|
||||||
|
initrd http://{{.baseURL}}/configs/static/windows/windows10/sources/boot.wim boot.wim
|
||||||
|
|
||||||
|
boot
|
||||||
|
{{end}}
|
7
shoelaces/shoelaces/mappings.yaml
Normal file
7
shoelaces/shoelaces/mappings.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
networkMaps:
|
||||||
|
- network: 172.18.57.0/24
|
||||||
|
script:
|
||||||
|
name: windows10.ipxe
|
||||||
|
# - network: 172.18.57.0/24
|
||||||
|
# script:
|
||||||
|
# name: hostname.ipxe
|
9
shoelaces/shoelaces/static/windows/windows10/install.bat
Normal file
9
shoelaces/shoelaces/static/windows/windows10/install.bat
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
wpeinit
|
||||||
|
ipconfig
|
||||||
|
echo "Sleeping for 10 seconds for network..."
|
||||||
|
echo wsh.sleep 10000 > sleep.vbs
|
||||||
|
cscript /nologo sleep.vbs
|
||||||
|
ipconfig
|
||||||
|
net use \\ucs\software
|
||||||
|
\\ucs\software\pxe\Windows10\setup.exe /unattend:unattend.xml
|
||||||
|
pause
|
@ -0,0 +1,2 @@
|
|||||||
|
[LaunchApps]
|
||||||
|
"install.bat"
|
43
autounattend.xml → shoelaces/shoelaces/windows10/unattend.xml.slc
Executable file → Normal file
43
autounattend.xml → shoelaces/shoelaces/windows10/unattend.xml.slc
Executable file → Normal file
@ -1,3 +1,4 @@
|
|||||||
|
{{define "windows10/unattend.xml" -}}
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||||
<settings pass="windowsPE">
|
<settings pass="windowsPE">
|
||||||
@ -116,7 +117,7 @@
|
|||||||
<CEIPEnabled>0</CEIPEnabled>
|
<CEIPEnabled>0</CEIPEnabled>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
<ComputerName>*</ComputerName>
|
<ComputerName>{{.hostname}}</ComputerName>
|
||||||
<ProductKey>W269N-WFGWX-YVC9B-4J6C9-T83GX</ProductKey>
|
<ProductKey>W269N-WFGWX-YVC9B-4J6C9-T83GX</ProductKey>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
@ -131,46 +132,12 @@
|
|||||||
<SkipMachineOOBE>true</SkipMachineOOBE>
|
<SkipMachineOOBE>true</SkipMachineOOBE>
|
||||||
<ProtectYourPC>1</ProtectYourPC>
|
<ProtectYourPC>1</ProtectYourPC>
|
||||||
</OOBE>
|
</OOBE>
|
||||||
<UserAccounts>
|
<RegisteredOrganization>Claremont MakerSpace</RegisteredOrganization>
|
||||||
<LocalAccounts>
|
<RegisteredOwner>Claremont MakerSpace</RegisteredOwner>
|
||||||
<LocalAccount wcm:action="add">
|
|
||||||
<Password>
|
|
||||||
<Value>REPLACE_WITH_LOCAL_ADMIN_PASSWORD</Value>
|
|
||||||
<PlainText>true</PlainText>
|
|
||||||
</Password>
|
|
||||||
<Description>Local Administrator</Description>
|
|
||||||
<DisplayName>Local Administrator</DisplayName>
|
|
||||||
<Group>Administrators;Power Users</Group>
|
|
||||||
<Name>LocalAdmin</Name>
|
|
||||||
</LocalAccount>
|
|
||||||
</LocalAccounts>
|
|
||||||
</UserAccounts>
|
|
||||||
<RegisteredOrganization></RegisteredOrganization>
|
|
||||||
<RegisteredOwner></RegisteredOwner>
|
|
||||||
<DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet>
|
<DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet>
|
||||||
<TimeZone>Eastern Standard Time</TimeZone>
|
<TimeZone>Eastern Standard Time</TimeZone>
|
||||||
<AutoLogon>
|
|
||||||
<Enabled>true</Enabled>
|
|
||||||
<LogonCount>2</LogonCount>
|
|
||||||
<Username>LocalAdmin</Username>
|
|
||||||
<Password>
|
|
||||||
<Value>REPLACE_WITH_LOCAL_ADMIN_PASSWORD</Value>
|
|
||||||
</Password>
|
|
||||||
</AutoLogon>
|
|
||||||
<FirstLogonCommands>
|
|
||||||
<SynchronousCommand wcm:action="add">
|
|
||||||
<CommandLine>net use \\ucs\software /user:DomainJoin REPLACE_WITH_PASSWORD</CommandLine>
|
|
||||||
<Description>Set Up UCS Share</Description>
|
|
||||||
<Order>1</Order>
|
|
||||||
</SynchronousCommand>
|
|
||||||
<SynchronousCommand wcm:action="add">
|
|
||||||
<CommandLine>powershell -NoExit -executionPolicy Bypass -File \\ucs.sawtooth.claremontmakerspace.org\Software\unattend\Set-ComputerName.ps1</CommandLine>
|
|
||||||
<Description>Set Computer Name</Description>
|
|
||||||
<Order>2</Order>
|
|
||||||
<RequiresUserInput>true</RequiresUserInput>
|
|
||||||
</SynchronousCommand>
|
|
||||||
</FirstLogonCommands>
|
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
<cpi:offlineImage cpi:source="wim:d:/sources/install.wim#Windows 10 Pro" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
|
<cpi:offlineImage cpi:source="wim:d:/sources/install.wim#Windows 10 Pro" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
|
||||||
</unattend>
|
</unattend>
|
||||||
|
{{end}}
|
BIN
tftp/ipxe.efi
Executable file
BIN
tftp/ipxe.efi
Executable file
Binary file not shown.
17
tftp/main.ipxe
Normal file
17
tftp/main.ipxe
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#!ipxe
|
||||||
|
:MENU
|
||||||
|
menu
|
||||||
|
item --gap -- ---------------- iPXE boot menu ----------------
|
||||||
|
item shoelaces Shoelaces
|
||||||
|
item shell ipxe shell
|
||||||
|
choose --default return --timeout 5000 target && goto ${target}
|
||||||
|
|
||||||
|
:shoelaces
|
||||||
|
chain http://cms-net-svcs.claremontmakerspace.org:8081/poll/1/${mac} ||
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:shell
|
||||||
|
shell ||
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
autoboot
|
1
tftp/tftpd.map
Normal file
1
tftp/tftpd.map
Normal file
@ -0,0 +1 @@
|
|||||||
|
rg (.*)<29>$ \1
|
Loading…
Reference in New Issue
Block a user