We create a universal Install Server for automatic network installation of Linux and Windows based on Cobbler
Cobbler is a tool in the Linux world that can be used as Install Server, creating many network installation scripts based on one or more Linux distributions. There is also support for installations of FreeBSD, VMware, Xen, and Nexenta.
With his help, I would also like to flexibly and universally create my own network installation scripts from different Windows distributions (XP, 2003, 7, 8, 2008, 2012).
About how to configure and use cobbler to install Linux is exhaustively written on its official website - https://cobbler.github.io . I will focus here on my version of the solution to the problem regarding Windows.
The main problem for creating your own network installation script was preparing the necessary Windows boot files in Linux. The generation of the necessary binaries for me occurs when I run the cobbler post-trigger on the command cobbler sync:
some files are created from standard ones by directly replacing one line with another right in the binary.
in the process of changing the bootmgr.exe file, the checksum of the PE file will change and it needs to be recalculated. The trigger does this with python-pefile.
hivex was used to create BCD boot information registries.
Windows response files are generated using cobbler templates using all its capabilities for conditional code generation depending on the version of Windows, architecture (32 or 64 bits), installation profile, etc.
startup scripts for wim images (startnet.cmd) and a script that runs after the installation of OS (post_install.cmd) are also generated from templates.
In my case, response files are generated mainly based on the version of Windows and quite rarely, additionally based on the profile name. Those. I chose for myself the most suitable and in most cases used installation options that can be specified in the answer file for each version of Windows.
The main work is to configure the installation option, install additional software, etc. lies on the script that I specify as the kickstart file for the cobbler profile (win.ks) in the case of installing Windows and downloading the post_install.cmd script already at the time of installation. Cobbler at the time of download dynamically generates code based on the win.ks template. And in this template, most code is already generated based on the profile name.
As a result, in most cases, to change the installation script during debugging, it is enough for me to simply edit the win.ks text file on the server and restart the installation.
Simplifying the logical relationship of the files that need to be changed for each scenario, the network installation for Xp and 2003 can be schematically depicted as follows:
For Windos 7 and newer:
Not the most, of course, a short way from the item in the PXE menu to running the post_install.cmd script with the profile name as a parameter. In addition, this script should also take the kickstart server cobbler corresponding to this profile and run it. pxeboot.n12 → setupldr.exe → winnt.sif → post_install.cmd profile_name
We believe that cobbler 2.6.9 and everything you need for its operation have already been installed, configured (for example iptables, SElinux) and does an excellent job of installing Linux.
Install everything you need:
# dnf install python-pefile hivex ntfs-3g fuse
wimlib I collected from source
Using hivex directly is not convenient - here (a good installation option, but I didn’t like it because it uses tftp log analysis, but I don’t need to tweak the binaries) I found a ready-made script (bcdedit.pl) that makes the necessary changes in the standard BCD . In fact, it is a replacement for the Windows utility bcdedit. It is written in Perl, I extracted it from the archive and placed it in / usr / local / bin, having modified it beforehand:
We will need ris-linux. In my distribution, it is put like this:
# dnf install ris-linux
So that RIS does not interfere with the installation of Win 7, you need to comment out one line in the /usr/share/ris-linux/binlsrv.py file:
# cd /usr/share/ris-linux
# cp binlsrv.py binlsrv.py.orig
# sed -i "s/p = p + chr(252) + chr(len(/#&/gi" binlsrv.py
# diff binlsrv.py.orig binlsrv.py
571c571
< p = p + chr(252) + chr(len('boot\\bcd')) + 'boot\\bcd'
---
> #p = p + chr(252) + chr(len('boot\\bcd')) + 'boot\\bcd'
I was not able to find an easy way, without analyzing tftpd logs, the ability to tell RIS which BCD is needed. Therefore, I just turned it off.
Create the folder / var / lib / tftpboot / winos / inf and copy there all the .inf files of the 32-bit network card drivers that you need and run the command:
No one bothered to rewrite the ris-linux service under systemd, however no one needs it anymore.
# cd /etc/rc.d/init.d
# cp ris-linuxd ris-linuxd64
# sed -i 's/ris-linuxd/&64/gi' ris-linuxd64
Create the folder / var / lib / tftpboot / winos / inf64 and copy there all the .inf files of the 64-bit network card drivers that you need and run the command:
Additionally, you need to change the port in the Win2K3-Server_EN-x64 bootloader to the one that we pointed to 64-bit RIS.
You can do this using the modldr.py utility from the ris-linux package:
# vi /etc/samba/smb.conf
# Дистрибутивы для инсталляции
[WINOS]
path = /var/lib/tftpboot/winos
guest ok = yes
browseable = yes
writeable = no
public = yes
blocking locks = no
oplocks = no
level2 oplocks = no
# Разный публично доступный софт
[public]
comment = Public Stuff
path = /var/www/html/Distr
public = yes
writable = no
printable = no
guest ok = yes
blocking locks = no
oplocks = no
level2 oplocks = no
We create a user with minimal rights, which we will use during installation. I have: install / install
Preparing Files for a Network Installation Win 7, 8, 2008, 2012
The situation with Win 7, 8, 2008, 2012 is much worse than in Xp. It’s not so easy to dig into binaries here - you need to then count the checksum in it.
The answer file for automatic installation here also has a more modern look - in xml format. It has many improvements, for example, you can specify how to cut disks into partitions, etc. But there is also a big fly in the ointment - the name of the answer file is sewn into the startup script, which actually starts the installation. In turn, the script itself is wired into the wim image.
Those. there were still big inconveniences with fixing the binaries, and in addition, for each installation scenario based on one distribution, I now have to have a separate wim image that differs from another same image only in the name of the response file in the start script.
In the directories with distributions you need to put the following files:
pxeboot.n12
bootmgr.exe
boot / bcd
boot / boot.sdi
boot / fonts and the font set in it
Creating Cobbler Templates
Create file name translation rules for tftp:
File Contents /etc/tftpd.rules
#vi /etc/tftpd.rules
rg \\ / # Convert backslashes to slashes
r (BOOTFONT\.BIN) /winos/\1
r (/Boot/Fonts/)(.*) /winos/Win8_RU-x64/boot/Fonts/\2
r (ntdetect\.wxp) /winos/\1
r (ntdetect\.2k3) /winos/\1
r (wine.\.sif) /WinXp_EN-i386/\1
r (xple.) /WinXp_EN-i386/\1
r (winr.\.sif) /WinXp_RU-i386/\1
r (xplr.) /WinXp_RU-i386/\1
r (wi2k.\.sif) /Win2K3-Server_EN-x64/\1
r (w2k3.) /Win2K3-Server_EN-x64/\1
r (/Win2K3-Server_EN-x64/)(.*) /winos\1\L\2
r (boot7r..exe) /winos/Win7_RU-x64/\1
r (/Boot/)(7R.) /winos/Win7_RU-x64/boot/\2
r (boot7e.\.exe) /winos/Win7_EN-x64/\1
r (/Boot/)(7E.) /winos/Win7_EN-x64/boot/\2
r (boot28.\.exe) /winos/Win2k8-Server_EN-x64/\1
r (/Boot/)(28.) /winos/Win2k8-Server_EN-x64/boot/\2
r (boot2e.\.exe) /winos/Win2012-Server_EN-x64/\1
r (/Boot/)(2e.) /winos/Win2012-Server_EN-x64/boot/\2
r (boot2r.\.exe) /winos/Win2012-Server_RU-x64/\1
r (/Boot/)(2r.) /winos/Win2012-Server_RU-x64/boot/\2
r (boot81.\.exe) /winos/Win8_RU-x64/\1
r (/Boot/)(B8.) /winos/Win8_RU-x64/boot/\2
r (/WinXp...-i386/)(.*) /winos\1\L\2
We specify conversion rules in the cobbler template for tftp:
# vi /etc/cobbler/tftpd.template
service tftp
{
disable = no
socket_type = dgram
protocol = udp
wait = yes
user = $user
server = $binary
server_args = -m /etc/tftpd.rules --port-range 25000:25030 -v -v -v -s $args
per_source = 11
cps = 100 2
flags = IPv4
}
We add version information in the /var/lib/cobbler/distro_signatures.json file in the windows section so that they can be used in the templates through cobbler metadata.
When setting up the Windows installation, it was possible to preserve the main advantages of installing Linux through cobbler by generating an answer file and a post-installation script from the templates.
The composition of the templates for Windows includes the following files:
post_inst_cmd.template - script template run after OS installation
win.ks - plays the role of the post section in kiskstart linux for windows
win_sif.template - response file template
startnet.template - start script template in wim image
winpe7.template - wim file for Win 7 and Win 2008 Server
winpe8.template - wim file for Win 8 and Win 2012 Server
Post_inst_cmd.template and win.ks templates
Create a template for the script that runs after installing windows (no matter which version).
At startup, the name of the profile (installation option) cobbler is passed as a parameter.
# cat /var/lib/tftpboot/winos/post_inst_cmd.template
%systemdrive%
CD %systemdrive%\TMP >nul 2>&1
$SNIPPET('my/win_wait_network_online')
wget.exe http://@@http_server@@/cblr/svc/op/ks/profile/%1
MOVE %1 install.cmd
todos.exe install.cmd
start /wait install.cmd
DEL /F /Q libeay32.dll >nul 2>&1
DEL /F /Q libiconv2.dll >nul 2>&1
DEL /F /Q libintl3.dll >nul 2>&1
DEL /F /Q libssl32.dll >nul 2>&1
DEL /F /Q wget.exe >nul 2>&1
DEL /F /Q %0 >nul 2>&1
A few explanations on the template:
In each directory with the distribution kit there is a folder $ OEM $ / $ 1 / TMP in which lies wget.exe, todos.exe and dlls that they need to run. More precisely, they lie in one folder, while others are just symlink to it.
# ls -l '/var/lib/tftpboot/winos/Win2K3-Server_EN-x64/$OEM$/$1/TMP'
-rwxr-xr-x. 1 root root 1177600 Sep 4 2008 libeay32.dll
-rwxr-xr-x. 1 root root 1008128 Mar 15 2008 libiconv2.dll
-rwxr-xr-x. 1 root root 103424 May 7 2005 libintl3.dll
-rwxr-xr-x. 1 root root 232960 Sep 4 2008 libssl32.dll
-rwxr-xr-x. 1 root root 4880 Oct 26 1999 sleep.exe
-rwxr-xr-x. 1 root root 52736 Oct 27 2013 todos.exe
-rwxr-xr-x. 1 root root 449024 Dec 31 2008 wget.exe
# ls -l '/var/lib/tftpboot/winos/Win8_RU-x64/sources/$OEM$/$1/TMP'
lrwxrwxrwx. 1 root root 45 Oct 28 2013 /var/lib/tftpboot/winos/Win8_RU-x64/sources/$OEM$/$1/TMP -> ../../../../Win2K3-Server_EN-x64/$OEM$/$1/TMP
# ls -l '/var/lib/tftpboot/winos/WinXp_RU-i386/$OEM$/$1/TMP'
lrwxrwxrwx. 1 root root 42 May 31 2014 /var/lib/tftpboot/winos/WinXp_RU-i386/$OEM$/$1/TMP -> ../../../Win2K3-Server_EN-x64/$OEM$/$1/TMP
When installing OS, these files are copied to C: \ TMP.
Snipet win_wait_network_online waits until the network is ready, pinging the IP address of the server where cobbler is installed.
Snippet file contents / var / lib / cobbler / snippets / my / win_wait_network_online
:wno10
set n=0
:wno20
ping @@http_server@@ -n 3
set exit_code=%ERRORLEVEL%
IF %exit_code% EQU 0 GOTO wno_exit
set /a n=n+1
IF %n% lss 30 goto wno20
pause
goto wno10
:wno_exit
When the script is generated, the variable name @@ http_server @@ from the cobbler metadata is replaced with the real IP address of the server on which cobbler is installed.
Next, wget.exe downloads the kickstart file. Unlike Linux kickstart, this one contains a .cmd file, but also generated on the fly by the cobbler server from the win.ks template.
This file is renamed, then transcoded to the windows text file format and run as a regular cmd script.
After working out the script in C: \ TMP, the files used to obtain it and the script itself, too, are deleted.
The file that we downloaded with wget and which plays the role of the post-installation part of kickstart Linux looks like this in a simplified form:
File Contents /var/lib/cobbler/kickstarts/win.ks
# cat /var/lib/cobbler/kickstarts/win.ks
$SNIPPET('my/win_wait_network_online')
set n=0
:mount_y
net use y: \\@@http_server@@\Public /user:install install
set exit_code=%ERRORLEVEL%
IF %exit_code% EQU 0 GOTO mount_z
set /a n=n+1
IF %n% lss 20 goto mount_y
PAUSE
goto mount_y
set n=0
:mount_z
net use z: \\@@http_server@@\winos /user:install install
set exit_code=%ERRORLEVEL%
IF %exit_code% EQU 0 GOTO mount_exit
set /a n=n+1
IF %n% lss 20 goto mount_z
PAUSE
goto mount_z
:mount_exit
if exist %systemdrive%\TMP\stage.dat goto flag005
echo 0 > %systemdrive%\TMP\stage.dat
$SNIPPET('my/win_check_virt')
#if $distro_name in ( 'WinXp_EN-i386', 'WinXp_RU-i386', 'Win2K3-Server_EN-x64' )
z:\Drivers\wsname.exe /N:$DNS /NOREBOOT
#else
REM pause
#end if
echo Windows Registry Editor Version 5.00 > %systemdrive%\TMP\install.reg
echo [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce] >> %systemdrive%\TMP\install.reg
echo "DD"="C:\\TMP\\install.cmd" >> %systemdrive%\TMP\install.reg
$SNIPPET('my/win_install_drivers')
#if $distro_name == 'Win2K3-Server_EN-x64'
start /wait z:\Win2K3-Server_EN-x64\cmpnents\r2\setup2.exe /q /a /sr
start /wait y:\Windows\Win2003\IE8-WindowsServer2003-x64-ENU.exe /passive /update-no /norestart
if %virt% equ NO REG IMPORT y:\Windows\Win2003\vm.reg
#end if
REG IMPORT %systemdrive%\TMP\install.reg
net use Y: /delete
net use Z: /delete
%systemdrive%\TMP\sleep.exe 10
exit
:flag005
for /f "tokens=*" %%i in (%systemdrive%\TMP\stage.dat) do set stage=%%i
echo 1 > %systemdrive%\TMP\stage.dat
REG IMPORT %systemdrive%\TMP\install.reg
if %stage% neq 0 goto flag010
net use Y: /delete
net use Z: /delete
shutdown -r -f -t 5
exit
:flag010
if %stage% gtr 1 goto flag020
echo 2 > %systemdrive%\TMP\stage.dat
$SNIPPET('my/winzip')
$SNIPPET('my/winrar')
$SNIPPET('my/win_install_chrome')
$SNIPPET('my/win_install_ffox')
$SNIPPET('my/win_install_adacr')
$SNIPPET('my/win_install_jdk7-x86')
$SNIPPET('my/win_install_jdk7-x86_64')
$SNIPPET('my/win_install_UltraVNC')
#if $distro_name in ( 'WinXp_EN-i386', 'WinXp_RU-i386', 'Win2K3-Server_EN-x64' )
$SNIPPET('my/win_install_office_2007_ru')
#else if $distro_name in ( 'Win7_RU-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )
$SNIPPET('my/win_install_office_2010_ru')
#else
$SNIPPET('my/win_install_office_2010_en')
#end if
Title Cleaning Temp files
DEL "%systemroot%\*.bmp" >nul 2>&1
DEL "%systemroot%\Web\Wallpaper\*.jpg" >nul 2>&1
DEL "%systemroot%\system32\dllcache\*.scr" >nul 2>&1
DEL "%systemroot%\system32\*.scr" >nul 2>&1
DEL "%AllUsersProfile%\Start Menu\Windows Update.lnk" >nul 2>&1
DEL "%AllUsersProfile%\Start Menu\Set Program Access and Defaults.lnk" >nul 2>&1
DEL "%AllUsersProfile%\Start Menu\Windows Catalog.lnk" >nul 2>&1
DEL "%systemdrive%\Microsoft Office*.txt" >nul 2>&1
net user aspnet /delete >nul 2>&1
REM %systemdrive%\TMP\sleep.exe 60
net use Y: /delete
net use Z: /delete
shutdown -r -f -t 30
RD /S /Q %systemdrive%\DRIVERS\ >nul 2>&1
if not defined stage DEL /F /Q %systemdrive%\post_install.cmd
DEL /F /S /Q %systemdrive%\TMP\*.*
exit
Snippets are used here so as not to clutter up the code. They carry out checks on the hypervisor, install the necessary drivers, depending on the distribution and profile, this or that software is installed or not installed.
Win_sif.template template
This is a single template from which the response files of all versions of windows are formed. In it, as in other templates, all cobbler meta-variables can be used.
Such as $ arch, $ distro_name, $ profile_name.
#else if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64', 'Win2k8-Server_EN-x64', 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )
#if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
#end if
#if $distro_name in ( 'Win7_RU-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )
0409:00000409;0419:00000419ru-RUru-RUru-RUru-RU
#else
0409:00000409en-USen-USen-USen-US
#end if
OnError1truePrimary0true/IMAGE/NAME
#if $profile_name == 'IOS'
Windows8_VmVare_MacOS_xCode
#else if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64' )
Windows 7 PROFESSIONAL
#else if $distro_name in ( 'Win2k8-Server_EN-x64' )
Windows Server 2008 R2 SERVERENTERPRISE
#else if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
Windows Server 2012 R2 SERVERDATACENTER
#else if $distro_name in ( 'Win8_RU-x64' )
Windows 8.1 Pro
#else if $distro_name in ( 'Win8_EN-x64' )
Windows 8.1 Enterprise
#end if
01
#if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
#end if
NevertrueUserMy Organizationfalse
#if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )
\\@@http_server@@\WINOS\Drivers\CHIPSET\Win8
#else
\\@@http_server@@\WINOS\Drivers\CHIPSET\5520\Vista\\@@http_server@@\WINOS\Drivers\CHIPSET\C200\WIN7
#end if
#if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )
\\@@http_server@@\WINOS\Drivers\NIC\Win8
#else
\\@@http_server@@\WINOS\Drivers\NIC
#end if
#if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64', 'Win2k8-Server_EN-x64' )
\\@@http_server@@\WINOS\Drivers\ACPI\64\WIN7
#end if
\\@@http_server@@\WINOS\Drivers\Storage\64
#if $distro_name in ( 'Win8_RU-x64' )
\\@@http_server@@\WINOS\Drivers\Virt\Win8
#else if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
\\@@http_server@@\WINOS\Drivers\Virt\2012
#else if $distro_name in ( 'Win2k8-Server_EN-x64' )
\\@@http_server@@\WINOS\Drivers\Virt\2008
#else
\\@@http_server@@\WINOS\Drivers\Virt\Win7
#end if
false*My OrganizationInstructor
#if $distro_name == 'Win7_RU-x64'
XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
#else if $distro_name == 'Win7_EN-x64'
XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
#else if $distro_name == 'Win2k8-Server_EN-x64'
XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
#else if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
#end if
WORKGROUPfalsetrueRemote Desktopall03WorkXXXXfalseXXXXXfalseUserAdministratorsWORKGOUPDomain AdminsAdministratorsUserAdministratorsCentral Asia Standard TimeMy OrganizationUserfalse1cmd /C wmic useraccount where "name='user'" set PasswordExpires=FALSEfalse2c:\post_install.cmd @@profile_name@@XXXXfalsetrueWORKGOUPUser10000
#if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64' )
#else if $distro_name in ( 'Win2k8-Server_EN-x64' )
#else if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
#else if $distro_name in ( 'Win8_RU-x64' )
#end if
#end if
Ключевой момент здесь в строках, при помощи которых стартует скрипт с именем профиля в качестве параметра:
wpeinit
ping 127.0.0.1 -n 10 >nul
md \tmp
cd \tmp
ipconfig /all | find "DHCP Server" > dhcp
ipconfig /all | find "IPv4 Address" > ipaddr
FOR /F "eol=- tokens=2 delims=:" %%i in (dhcp) do set dhcp=%%i
FOR %%i in (%dhcp%) do set dhcp=%%i
FOR /F "eol=- tokens=2 delims=:(" %%i in (ipaddr) do set ipaddr=%%i
net use y: \\%dhcp%\Public /user:install install
net use z: \\%dhcp%\WINOS\@@distro_name@@ /user:install install
set exit_code=%ERRORLEVEL%
IF %exit_code% EQU 0 GOTO GETNAME
echo "Can't mount network drive
goto EXIT
:GETNAME
y:\windows\bind\nslookup.exe %ipaddr% | find "name =" > wsname
for /f "eol=- tokens=2 delims==" %%i in (wsname) do echo %%i > ws
for /f "eol=- tokens=1 delims=." %%i in (ws) do set wsname=%%i
FOR %%i in (%wsname%) do set wsname=%%i
#set $unattended = "set UNATTENDED_ORIG=Z:\\sources\\" + $kernel_options["sif"]
$unattended
set UNATTENDED=X:\tmp\autounattended.xml
echo off
FOR /F "tokens=1 delims=!" %%l in (%UNATTENDED_ORIG%) do (
IF "%%l"==" *" (
echo ^%wsname%^<^/ComputerName^>>> %UNATTENDED%
) else (
echo %%l>> %UNATTENDED%
)
)
echo on
:INSTALL
set n=0
z:\sources\setup.exe /unattend:%UNATTENDED%
set /a n=n+1
ping 127.0.0.1 -n 5 >nul
IF %n% lss 20 goto INSTALL
:EXIT
Шаблоны winpe7.template и winpe8.template
Стандартные образы для сетевой инсталляции Win 7 и Win 8 с интегрированными драйверами.
По команде cobbler sync их копируют в место указанное в каждом профиле.
Каждую такую копию монтируют, в нее копируют стартовый скрипт созданный на основе шаблона startnet.template и демонтируют в триггере на post-sync.
Для работы с wim образами в Linux я использую wimlib
Для ее работы нужно установить пакет ntfs-3g, который в свою очередь работает через fuse.
Поэтому если как я, будете ставить cobbler в linux контейнере lxc под управлением libvirt, то нужно прописать в xml определения домена строки:
Здесь kernel фиктивный — PXE будет загружать файл созданный на основе этого pxeboot.n12 по команде cobbler sync.
/var/lib/tftpboot/winos/add_ram.dat тоже фиктивный initrd, в смысле можно подсунуть любой— иначе distro не создать.
#if $profile_name == ' Win8-test1'
#end if
#if $profile_name == ' Win8-test2'
#end if
Имена файлов в профилях реальные и должны соответствовать шаблонам, определенным в /etc/tftpd.rules для данного дистрибутива. Только вот самих файлов пока еще нет, их создаст триггер в момент выполнения команды cobbler sync.
Шаблон меню PXE загрузки
Cobbler по умолчанию создает меню в виде списка и чтобы сделать его более удобным нужно еще немного потрудиться.
Почему-то разработчики cobbler при генерации pxe меню из шаблона в качестве значения переменной http_server устанавливают фиксированное значение server.example.org, а не берут его из конфигурационного файла. Windows пунктам меню это не мешает, а вот для Linux при помощи этого значения можно указать местоположение kickstart файла. Исправляем это:
# cd /usr/lib/python2.7/site-packages/cobbler
# cp templar.py templar.py.orig
# sed -i 's/"server.example.org"/self.settings.server/gi' templar.py
Теперь из файла /etc/cobbler/pxe/pxedefault.template строку
$pxe_menu_items
можно убрать, а вместо нее нарисовать что-нибудь свое:
menu begin Linux
MENU TITLE Linux
label Fedora-latest-x86_64
MENU INDENT 5
MENU LABEL Fedora-latest-x86_64
kernel /images/Fedora-22-x86_64/vmlinuz
append initrd=/images/Fedora-22-x86_64/initrd.img ks.device=bootif ks.sendmac lang=en text ks=http://@@http_server@@/cblr/svc/op/ks/profile/Fedora-latest-x86_64
ipappend 2
label returntomain
menu label Return to ^main menu.
menu exit
menu end
menu begin Windows
MENU TITLE Windows
label Win8-test1
MENU INDENT 5
MENU LABEL Win8-test1
kernel /winos/Win8_RU-x64/win81b.0
label Win8-test2
MENU INDENT 5
MENU LABEL Win8-test2
kernel /winos/Win8_RU-x64/win81c.0
label returntomain
menu label Return to ^main menu.
menu exit
menu end
Собираем все вместе
Теперь из шаблонов будем создавать загрузочные файлы, скрипты, образы wim для загрузки и поместим все это в нужные места.
Для этого я решил воспользоваться триггером cobbler на post sync:
import distutils.sysconfig
import sys
import os
import traceback
import cexceptions
import os
import re
import xmlrpclib
import pefile
import cobbler.module_loader as module_loader
import cobbler.utils as utils
import cobbler.config as config
import cobbler.templar as templar
template_dir = "/var/lib/tftpboot/winos/"
sif_template_name = template_dir + "win_sif.template"
post_inst_cmd_template_name = template_dir + "post_inst_cmd.template"
startnet_template_name = template_dir + "startnet.template"
wim7_template_name = template_dir + "winpe7.template"
wim8_template_name = template_dir + "winpe8.template"
wimlib = "/usr/bin/wimlib-imagex"
wimlib_mount = wimlib + " mountrw"
wimlib_umount = wimlib + " unmount"
mount_point = "/mnt/wim"
bcdedit = "/usr/local/bin/bcdedit.pl"
plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
def register():
# this pure python trigger acts as if it were a legacy shell-trigger, but is much faster.
# the return of this method indicates the trigger type
return "/var/lib/cobbler/triggers/sync/post/*"
def run( api, args, logger ):
settings = api.settings()
images = api.images()
distros = api.distros()
profiles = api.profiles()
conf = config.Config( api )
templ = templar.Templar( conf )
rc = 0
template_win = open( post_inst_cmd_template_name )
tmpl_data = template_win.read()
template_win.close()
for distro in distros:
if distro.breed == "windows":
meta = utils.blender( api, False, distro )
if distro.kernel_options.has_key( "post_install" ):
data = templ.render( tmpl_data, meta, None, distro )
pi_file = open( distro.kernel_options["post_install"], "w+" )
pi_file.write( data )
pi_file.close()
template_win = open( sif_template_name )
tmpl_data = template_win.read()
template_win.close()
template_start = open( startnet_template_name )
tmplstart_data = template_start.read()
template_start.close()
logger.info( "\nWindows profiles:" )
for profile in profiles:
distro = profile.get_conceptual_parent()
if distro.breed == "windows":
logger.info( 'Profile: ' + profile.name )
meta = utils.blender( api, False, profile )
(distro_path, pxeboot_name) = os.path.split( distro.kernel )
if profile.kernel_options.has_key( "sif" ):
data = templ.render( tmpl_data, meta, None, profile )
if distro.os_version in ( "7", "2008", "8", "2012" ):
sif_file_name = os.path.join( distro_path, 'sources', profile.kernel_options["sif"] )
else:
sif_file_name = os.path.join( distro_path, profile.kernel_options["sif"] )
sif_file = open(sif_file_name, "w+" )
sif_file.write( data )
sif_file.close()
logger.info( 'Build answer file: ' + sif_file_name )
if profile.kernel_options.has_key( "pxeboot" ) and profile.kernel_options.has_key( "bootmgr" ):
wk_file_name = os.path.join( distro_path, profile.kernel_options["pxeboot"] )
wl_file_name = os.path.join( distro_path, profile.kernel_options["bootmgr"] )
logger.info( "Build PXEBoot: " + wk_file_name )
if distro.os_version in ( "7", "2008", "8", "2012" ):
if len(profile.kernel_options["bootmgr"]) != 11:
logger.error( "The loader name should be EXACTLY 11 character" )
return 1
if profile.kernel_options.has_key( "bcd" ):
if len(profile.kernel_options["bcd"]) != 3:
logger.error( "The BCD name should be EXACTLY 5 character" )
return 1
tl_file_name = os.path.join( distro_path, 'bootmgr.exe' )
pat1 = re.compile( r'bootmgr\.exe', re.IGNORECASE )
pat2 = re.compile( r'(\\.B.o.o.t.\\.)(B)(.)(C)(.)(D)', re.IGNORECASE )
bcd_name = 'BCD'
if profile.kernel_options.has_key( "bcd" ):
bcd_name = profile.kernel_options["bcd"]
bcd_name = "\\g<1>" + bcd_name[0] + "\\g<3>" + bcd_name[1] + "\\g<5>" + bcd_name[2]
data = open( tl_file_name, 'rb').read()
out = pat2.sub( bcd_name, data )
else:
if len(profile.kernel_options["bootmgr"]) != 5:
logger.error( "The loader name should be EXACTLY 5 character" )
return 1
if len(profile.kernel_options["sif"]) != 9:
logger.error( "The response should be EXACTLY 9 character" )
return 1
tl_file_name = os.path.join( distro_path, 'setupldr.exe' )
pat1 = re.compile( r'NTLDR', re.IGNORECASE )
pat2 = re.compile( r'winnt\.sif', re.IGNORECASE)
data = open( tl_file_name, 'rb').read()
out = pat2.sub( profile.kernel_options["sif"], data )
logger.info( 'Build Loader: ' + wl_file_name )
if out != data:
open(wl_file_name, 'wb+').write(out)
if distro.os_version in ( "7", "2008", "8", "2012" ):
pe = pefile.PE( wl_file_name, fast_load=True )
pe.OPTIONAL_HEADER.CheckSum = pe.generate_checksum()
pe.write( filename=wl_file_name )
data = open(distro.kernel, 'rb').read()
out = pat1.sub( profile.kernel_options["bootmgr"], data )
if out != data:
open(wk_file_name, 'wb+').write(out)
if profile.kernel_options.has_key( "bcd" ):
obcd_file_name = os.path.join( distro_path, 'boot', 'BCD' )
bcd_file_name = os.path.join( distro_path, 'boot', profile.kernel_options["bcd"] )
wim_file_name = 'winpe.wim'
if profile.kernel_options.has_key( "winpe" ):
wim_file_name = profile.kernel_options["winpe"]
wim_file_name = os.path.join( '/winos', distro.name, 'boot', wim_file_name )
sdi_file_name = os.path.join( '/winos', distro.name, 'boot', 'boot.sdi' )
logger.info( 'Build BCD: ' + bcd_file_name + ' for ' + wim_file_name )
cmd = "/usr/bin/cp " + obcd_file_name + " " + bcd_file_name
rc = utils.subprocess_call( logger, cmd, shell=True )
cmd = bcdedit + " " + bcd_file_name + " " + wim_file_name + " " + sdi_file_name
rc = utils.subprocess_call( logger, cmd, shell=True )
ps_file_name = os.path.join( distro_path, "boot", profile.kernel_options["winpe"] )
if distro.os_version in ( "7", "2008" ):
wim_pl_name = wim7_template_name
elif distro.os_version in ( "8", "2012" ):
wim_pl_name = wim8_template_name
cmd = "/usr/bin/cp " + wim_pl_name + " " + ps_file_name
rc = utils.subprocess_call( logger, cmd, shell=True )
if os.path.exists( wimlib ):
cmd = wimlib_mount + " " + ps_file_name + " " + mount_point
rc = utils.subprocess_call( logger, cmd, shell=True )
data = templ.render( tmplstart_data, meta, None, profile )
pi_file = open( mount_point + "/Windows/System32/startnet.cmd", "w+" )
pi_file.write( data )
pi_file.close()
cmd = wimlib_umount + " " + mount_point + " --commit --rebuild"
rc = utils.subprocess_get( logger, cmd, shell=True )
return 0
Это обычный скрипт на python использующий Cobbler API. Для своей работы использует pefile для пересчета контрольной суммы в bootmgr.exe, bcdedit.pl для внесения изменений в BCD и библиотеку wimlib для монтирования образа и копирования созданного из шаблона файла startnet.cmd в папку Windows/System32.
Теперь создаем виртуалку с инсталляцией по сети, либо на обычном компьютере жмем кнопки Reset, F12 (ну или что у вас в BIOS для этих целей прописано), выбираем нужный пункт в меню и наслаждаемся автоматической сетевой инсталляцией как Linux так и Windows.
При необходимости внести изменения в сценарий пользуемся условной генерацией кода в win.ks. У меня в шапке этого файла сначала идет общая часть нужная при любом варианте инсталляции, в потом длинный if/else:
Дублирующейся, либо логически замкнутый код вынесен в снипеты.
Чего не хватает
Не будет работать импорт Windows дистрибутива. Новые версии Microsoft выпускает не так уж и часто, так что это можно пережить.
Не будет работать (даже не пытался проверять) создание ISO образа Windows дистрибутива.
Автоматическая генерация PXE меню в cobbler в случае Windows профилей будет работать неправильно.
Если не соблюдать правила формирования имен загрузочных файлов, придется изменять /etc/tftpd.rules. А при совпадении имен загрузочных файлов в рамках одного дистрибутива, файлы одного профиля могут перезаписаться файлами другого профиля.