Создаем универсальный Install Server для автоматической сетевой инсталляции Linux и Windows на основе Cobbler

от автора

Cobbler — инструмент в мире Linux, который можно использовать как Install Server, создания многих сценариев инсталляции по сети на основе одного или нескольких дистрибутивов Linux. Есть также поддержка инсталляций FreeBSD, VMware, Xen и Nexenta.

Хотелось бы при помощи него также гибко и универсально создавать свои сценарии сетевой инсталляции с разных дистрибутивов Windows (XP, 2003, 7, 8, 2008, 2012).

Про то как настроить и использовать cobbler для инсталляции Linux исчерпывающе написано на его официальном сайте — https://cobbler.github.io. Я же здесь сосредоточусь на своем варианте решения задачи относительно Windows.

Основной проблемой для создания своего сценария сетевой инсталляции была подготовка необходимых загрузочных файлов Windows в среде Linux. Генерация необходимых бинарных файлов у меня происходит при запуске post-триггера cobbler на команду cobbler sync:

  • часть файлов создается из стандартных прямой заменой одной строки на другую прямо в бинарнике.
  • в процессе изменения файла bootmgr.exe изменится контрольная сумма PE файла и ее нужно пересчитать. Триггер это делает при помощи python-pefile.
  • для создания реестров загрузочной информации BCD использовался hivex.
  • для работы с wim образоми — wimlib
  • файлы ответов Windows генерируются при помощи шаблонов cobbler с использованием всех его возможностей по условной генерации кода в зависимости от версии Windows, архитектуры (32 или 64 бита), профиля инсталляции и т. д.
  • стартовые скрипты для образов wim (startnet.cmd) и скрипта, запускаемого по окончании инсталляции OS (post_install.cmd) также генерируются из шаблонов.

В моем случае файлы ответов генерируются в основном на основе версии Windows и довольно редко, дополнительно на основе имени профиля. Т.е. я выбрал для себя наиболее подходящие и в большинстве случаев используемые опции инсталляции, которые можно указать в файле ответов для каждой версии Windows.

Основная же работа по настройке варианта инсталляции, установке дополнительного ПО и т.д. лежит на скрипте, который я указываю как kickstart файл для профиля cobbler (win.ks) в случае инсталляции Windows и скачиваемого скриптом post_install.cmd уже в момент инсталляции. Cobbler в момент скачивания динамически генерирует код на основе шаблона win.ks. А в этом шаблоне большинство кода генерируется уже на основе имени профиля.

В результате, в большинстве случаев для изменения сценария инсталляции при отладке мне достаточно просто подредактировать текстовый файл win.ks на сервере и заново запустить инсталляцию.

Упрощая до логической взаимосвязи файлов, которые нужно изменить для каждого сценария, сетевую инсталляцию для Xp и 2003 можно схематично изобразить так:
pxeboot.n12 → setupldr.exe → winnt.sif → post_install.cmd profile_name

Для Windos 7 и новее:
pxeboot.n12 → bootmgr.exe → BCD → winpe.wim → startnet.cmd → autounattended.xml → post_install.cmd profile_name

Не самый, конечно короткий путь от пункта в PXE меню до запуска скрипта post_install.cmd с именем профиля в качестве параметра. Кроме того этот скрипт должен еще взять у cobbler сервера kickstart соответствующий этому профилю и запустить его.

Теперь обо всем по порядку

Считаем что cobbler 2.6.9 и все необходимое для его работы у вас уже установлено, настроено (например iptables, SElinux) и прекрасно справляется с инсталляцией Linux.

Устанавливаем все необходимое:

  • # dnf install python-pefile hivex ntfs-3g fuse
  • wimlib я собирал из исходников

Использовать hivex напрямую не удобно — здесь (неплохой вариант инсталляции, но мне не понравился из-за того, что использует анализ логов tftp, зато бинарники корежить не нужно) я нашел готовый скрипт (bcdedit.pl), который делает необходимые изменения в стандартном BCD. По сути он является заменой Windows утилиты bcdedit. Написан он на Perl, я его извлек из архива и поместил в /usr/local/bin, предварительно видоизменив:

# diff -c bcdedit.pl.orig bcdedit.pl  *** bcdedit.pl.orig --- bcdedit.pl *************** *** 232,237 **** --- 232,238 ----   &AddElement($BCDFILE,$guids{bootmgr},"25000004","hex:3:1e,00,00,00,00,00,00,00");   &AddElement($BCDFILE,$guids{bootmgr},"12000004","string:Windows Boot Manager");   &AddElement($BCDFILE,$guids{bootmgr},"24000001",&Guids2MultiSZ($newguid)); + &AddElement($BCDFILE,$guids{bootmgr},"16000048","hex:3:01");      print "Creating New Object\n";   &CreateGuid($BCDFILE,$newguid,"0x10200003"); 

Добавленная строка аналогична выполнению команды:

 bcdedit -set {bootmgr} nointegritychecks Yes 

В каталоге tftp сервера создаем папку, где у нас будут лежать дистрибутивы Windows:

 # mkdir /var/lib/tftpboot/winos 

Теперь там создаем каталоги для каждого дистрибутива и копируем туда его содержимое.
У меня это выглядит так:

 # ls -l /var/lib/tftpboot/winos dr-xr-xr-x.  6 root    root               4096 Nov 29  2014 Win2012-Server_EN-x64 dr-xr-xr-x.  6 root    root               4096 Jun  1  2014 Win2012-Server_RU-x64 dr-xr-xr-x. 10 root    root               4096 May  6 19:41 Win2K3-Server_EN-x64 dr-xr-xr-x.  7 root    root               4096 Nov 13  2013 Win2k8-Server_EN-x64 dr-xr-xr-x.  4 root    root               4096 Oct 28  2013 Win7_EN-x64 dr-xr-xr-x.  5 root    root               4096 Sep 25  2014 Win7_RU-x64 dr-xr-xr-x.  6 root    root               4096 Jun 25 10:29 Win8_RU-x64 dr-xr-xr-x.  7 root    root               4096 Dec  8  2011 WinXp_EN-i386 dr-xr-xr-x.  8 root    root               4096 Jul 31 17:12 WinXp_RU-i386 

Если вы планируете инсталлировать такие раритеты как Xp или 2003

Подготовка загрузочных файлов

В каталогах с дистрибутивами WinXp_EN-i386, WinXp_RU-i386 и Win2K3-Server_EN-x64 выполняем следующие команды:

 [root@is WinXp_EN-i386]# cabextract i386/startrom.n1_ [root@is WinXp_EN-i386]# mv startrom.n12 pxeboot.n12 [root@is WinXp_EN-i386]# cabextract i386/setupldr.ex_ 

В каталогах дистрибутивов с Xp:

 [root@is WinXp_EN-i386]# sed -i 's/ntdetect\.com/ntdetect.wxp/gi setupldr.exe 

В каталогах дистрибутивов с 2003:

 [root@is Win2K3-Server_EN-x64]# sed -i 's/ntdetect\.com/ntdetect.2k3/gi setupldr.exe 

 # cp /var/lib/tftpboot/winos/WinXp_EN-i386/i386/ntdetect.com /var/lib/tftpboot/winos/ntdetect.wxp # cp /var/lib/tftpboot/winos/Win2K3-Server_EN-x64/i386/ntdetect.com /var/lib/tftpboot/winos/ntdetect.wxp 

Настройка RIS

Нам понадобиться ris-linux. В моем дистрибутиве он ставиться так:

 # dnf install ris-linux 

Чтобы RIS не мешал инсталляции Win 7, нужно в файле /usr/share/ris-linux/binlsrv.py закомментировать одну строчу:

 # 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' 

Найти простой способ, без анализа логов tftpd, возможность указать RIS какой же BCD нужен, мне не удалось. Поэтому просто отключил.

Создаем папку /var/lib/tftpboot/winos/inf и копируем туда все .inf файлы 32-битных драйверов сетевых карт, которые вам нужны и запускаем команду:

 # /usr/share/ris-linux/infparser.py /var/lib/tftpboot/winos/inf 

Мне нужно чтобы RIS одновременно обслуживал 32-битную Xp и 64-битный Server 2003. Для этого создадим еще один экземпляр сервиса на другом порту:

 # cd /etc/sysconfig # cp ris-linuxd ris-linuxd64 # sed -i 's/\/inf/&64/gi' ris-linuxd64 # sed -i 's/linuxd/&64/gi' ris-linuxd64 # sed -i 's/BINLSRV_OPTS=/&--port=4012/gi' ris-linuxd64 

# diff ris-linuxd ris-linuxd64 2c2 < # ris-linuxd service. --- > # ris-linuxd64 service. 6c6 < BINLSRV_INFPATH=/var/lib/tftpboot/winos/inf --- > BINLSRV_INFPATH=/var/lib/tftpboot/winos/inf64 9c9 < BINLSRV_OPTS= --- > BINLSRV_OPTS=--port=4012 12c12 < BINLSRV_LOGFILE=/var/log/ris-linuxd.log --- > BINLSRV_LOGFILE=/var/log/ris-linuxd64.log 15c15 < BINLSRV_PIDFILE=/var/run/ris-linuxd.pid --- > BINLSRV_PIDFILE=/var/run/ris-linuxd64.pid 

 # cd /usr/sbin # ln -s ris-linuxd ris-linuxd64 

Никто не удосужился переписать сервис ris-linux под systemd, впрочем никому это уже не надо.

 # cd /etc/rc.d/init.d # cp ris-linuxd ris-linuxd64 # sed -i 's/ris-linuxd/&64/gi' ris-linuxd64 

Создаем папку /var/lib/tftpboot/winos/inf64 и копируем туда все .inf файлы 64-битных драйверов сетевых карт, которые вам нужны и запускаем команду:

 /usr/share/ris-linux/infparser.py /var/lib/tftpboot/winos/inf64 

Включаем и запускаем сервисы:

 # systemctl enable  ris-linuxd # systemctl start  ris-linuxd # systemctl enable  ris-linuxd64 # systemctl start  ris-linuxd64 

Дополнительно нужно поменять порт в загрузчике Win2K3-Server_EN-x64 на тот что мы указали 64-битному RIS.
Сделать это можно помощью утилиты modldr.py из пакета ris-linux:

 # /usr/share/ris-linux/modldr.py -p 4012 /var/lib/tftpboot/winos/Win2K3-Server_EN-x64/setupldr.exe 

Если работа утилиты оканчивается ошибкой, то в тексте скрипта modldr.py замените строку:

ppattern = re.compile(r'\x6a\x04\x68(..)\x00\x00\xff\x35', re.DOTALL) 

на строку:

ppattern = re.compile(r'\x6a\x64\x68(..)\x00\x00\xff\x35', re.DOTALL) 

Подготовка SAMBA

Samba нам понадобится для того чтобы сделать общедоступными две папки

 # 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 

Создаем пользователя с минимальными правами, которого будем использовать при инсталляции. У меня: install/install

Подготовка файлов для сетевой инсталляции Win 7, 8, 2008, 2012

Ситуация с Win 7, 8, 2008, 2012 значительно хуже чем в Xp. Здесь так просто в бинарниках не поковыряешься — нужно потом контрольную сумму в нем пересчитывать.

Файл ответов для автоматической инсталляции тут тоже есть и более современного вида — в xml формате. В нем много улучшений, например можно указать как нужно диски на разделы порезать и т. д. Но есть и большая ложка дегтя — имя файла ответов зашито в стартовый скрипт, которой собственно и начинает инсталляцию. В свою очередь сам скрипт зашит в wim образ.

Т.е. остались еще большие неудобства с исправлением бинарников, а кроме того для каждого сценария инсталляции на основе одного дистрибутива я теперь должен иметь отдельный wim образ, который отличается от другого такого же образа только именем файла ответов в стартовом скрипте.

В каталоги с дистрибутивами нужно положить следующие файлы:

  • pxeboot.n12
  • bootmgr.exe
  • boot/BCD
  • boot/boot.sdi
  • boot/Fonts и набор шрифтов в нем

Создание шаблонов Cobbler

Создаем правила преобразования имен файлов для tftp:

Содержимое файла /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 

Указываем правила преобразования в шаблоне cobbler для 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 } 

Добавляем в файле /var/lib/cobbler/distro_signatures.json в раздел windows информацию о версиях, чтобы через метаданные cobbler их можно было использовать в шаблонах.

 # vi /var/lib/cobbler/distro_signatures.json   "windows": {    "2003": {    },    "2008": {    },    "2012": {    },    "XP": {    },    "7": {    },    "8": {    }   }, 

При настройке инсталляции Windows удалось сохранить основные преимущества инсталляции Linux через cobbler формированием файла ответов и пост-инсталляционного скрипта из шаблонов.

В состав шаблонов для Windows у меня входят следующие файлы:

  1. post_inst_cmd.template — шаблон скрипта запускаемого после инсталляции OS
  2. win.ks — играет роль раздела post в kiskstart linux для windows
  3. win_sif.template — шаблон файлов ответов
  4. startnet.template — шаблон стартового скрипта в wim образе
  5. winpe7.template — wim файл для Win 7 и Win 2008 Server
  6. winpe8.template — wim файл для Win 8 и Win 2012 Server

Шаблоны post_inst_cmd.template и win.ks

Создаем шаблон скрипта, запускаемого после установки windows (неважно какой версии).
Скрипту при запуске передается имя профиля (варианта инсталляции) cobbler в качестве параметра.

 # 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 

Немного пояснений по шаблону:
В каждом каталоге с дистрибутивом есть папка $OEM$/$1/TMP в которой лежит wget.exe, todos.exe и dll’ки необходимые им для запуска. Точнее лежат в одной папке, а другие просто symlink на нее.

 # 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 

  • При инсталляции OS эти файлы копируются в C:\TMP.
  • Снипет win_wait_network_online дожидается готовности сети пингуя IP адрес сервера где установлен cobbler.
    Содержимое файла снипета /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 

    При генерации скрипта имя переменной @@http_server@@ из метаданных cobbler заменяется на реальный IP адрес сервера на котором установлен cobbler.

  • Далее wget.exe скачивает kickstart файл. В отличие от Linux kickstart этот содержит .cmd файл, но тоже формируемый на лету сервером cobbler из шаблона win.ks.
  • Этот файл переименовывается, затем перекодируется в формат текстового файла windows и запускается как обычный cmd скрипт.
  • После отработки скрипта в C:\TMP удаляются файлы использованные для его получения и сам скрипт тоже.

Файл, который мы скачивали wget’ом и который играет роль пост-инсталляционной части kickstart Linux выглядит в упрощенном виде так:

Содержимое файла /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 

Снипеты здесь используются для того чтобы не загромождать код. В них проводится проверки на гипервизор, ставятся нужные драйвера, в зависимости от дистрибутива и профиля ставится или не ставится то или иное ПО.

Шаблон win_sif.template

Это единый шаблон из которого формируются файлы ответов всех версий windows. В нем, как и в других шаблонах можно использовать все мета-переменные cobbler.
Такие, например как $arch, $distro_name, $profile_name.

Содержимое файла /var/lib/tftpboot/winos/win_sif.template

 #if $distro_name in ( 'WinXp_EN-i386', 'WinXp_RU-i386', 'Win2K3-Server_EN-x64' ) #if $arch == 'x86_64' 	#set $win_arch = 'amd64' #else if $arch == 'i386' 	#set $win_arch = 'i386' #end if  #set $OriSrc = '\\\\' + $http_server + '\\WINOS\\' + $distro_name + '\\' + $win_arch #set $DevSrc = '\\Device\\LanmanRedirector\\' + $http_server + '\\WINOS\\' + $distro_name  [Data] floppyless = "1" msdosinitiated = "1" ; Needed for second stage OriSrc="$OriSrc" OriTyp="4" LocalSourceOnCD=1 DisableAdminAccountOnDomainJoin=0 AutomaticUpdates="No" Autopartition="0" UnattendedInstall="Yes"  [SetupData] OsLoadOptions = "/noguiboot /fastdetect" ; Needed for first stage SetupSourceDevice = "$DevSrc"  [Unattended] CrashDumpSetting=0 FactoryMode=No UnattendMode=FullUnattended UnattendSwitch="Yes" OemPreinstall="Yes" OemSkipEula="Yes" Repartition=No FileSystem=* WaitForReboot="No" NoWaitAfterTextMode=1 NoWaitAfterGUIMode=1 DriverSigningPolicy=Ignore NonDriverSigningPolicy=Ignore UpdateInstalledDrivers=Yes TargetPath=\WINDOWS OemPnPDriversPath=DRIVERS\NIC;DRIVERS\ACPI;DRIVERS\CHIPSET\5520\All;DRIVERS\CHIPSET\C200\All;DRIVERS\Storage;DRIVERS\Virt  #if $os_version == '2003' [LicenseFilePrintData] AutoMode = PerSeat #end if  [Display] BitsPerPel=32 XResolution=1440 YResolution=900 Vrefresh=60  [WindowsFirewall] Profiles = WindowsFirewall.TurnOffFirewall  [WindowsFirewall.TurnOffFirewall] Mode = 0  [PCHealth] RA_AllowToGetHelp=0  [GuiRunOnce] "%Systemdrive%\post_install.cmd @@profile_name@@"  [GuiUnattended] AdminPassword=* TimeZone=195 OEMSkipRegional=1 OemSkipWelcome=1 #if $os_version != '2003' AutoLogon = Yes AutoLogonCount=1 #end if  [RemoteInstall] Repartition=Yes UseWholeDisk=Yes  [Components] msmsgs=Off msnexplr=Off zonegames=Off Paint=Off #if $os_version == '2003' ; Iis_common=On ; Iis_inetmgr=On ComPlusNetwork=On ; Iis_www=On ; Iis_asp=On IEHardenAdmin=Off IEHardenUser=Off #end if  [TerminalServices] AllowConnections=1  [UserData] #if $os_version == '2003' ProductKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" #else if $distro_name == 'WinXp_EN-i386' ProductKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" #else if $distro_name == 'WinXp_RU-i386' ProductKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" #end if ComputerName=* FullName="Admin" OrgName="Microsoft"  [RegionalSettings] LanguageGroup=1,2,3,4,5 #if $distro_name == 'WinXp_RU-i386' SystemLocale=00000419 UserLocale=00000419 #else SystemLocale=00000409 UserLocale=00000409 #end if InputLocale=0409:00000409,0419:00000419  [Shell] CustomDefaultThemeFile="%WinDir%\Resources\Themes\Windows Classic.Theme"  [Networking] InstallDefaultComponents="Yes" 

#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' ) <?xml version="1.0" encoding="utf-8"?> <unattend xmlns="urn:schemas-microsoft-com:unattend"> #if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )     <servicing>         <package action="configure">             <assemblyIdentity name="Microsoft-Windows-ServerCore-Package" version="6.3.9600.16384" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" />             <selection name="IIS-WebServerRole" state="true" />             <selection name="Microsoft-Hyper-V" state="true" />             <selection name="Microsoft-Hyper-V-Offline" state="true" />             <selection name="Microsoft-Hyper-V-Online" state="true" />             <selection name="Microsoft-Hyper-V-Management-Clients" state="true" />             <selection name="Microsoft-Hyper-V-Management-PowerShell" state="true" />             <selection name="VmHostAgent" state="true" />             <selection name="FailoverCluster-FullServer" state="true" />             <selection name="FailoverCluster-PowerShell" state="true" />             <selection name="FailoverCluster-CmdInterface" state="true" />             <selection name="MultipathIo" state="true" />             <selection name="ServerManager-Core-RSAT-Role-Tools" state="true" />             <selection name="RSAT-Hyper-V-Tools-Feature" state="true" />             <selection name="ServerManager-Core-RSAT" state="true" />             <selection name="ServerMediaFoundation" state="true" />             <selection name="Remote-Desktop-Services" state="true" />             <selection name="IIS-WebServer" state="true" />             <selection name="IIS-ApplicationDevelopment" state="true" />             <selection name="IIS-CommonHttpFeatures" state="true" />             <selection name="IIS-HealthAndDiagnostics" state="true" />             <selection name="IIS-Performance" state="true" />             <selection name="IIS-Security" state="true" />             <selection name="IIS-WebServerManagementTools" state="true" />         </package>     </servicing> #end if     <settings pass="windowsPE">         <component name="Microsoft-Windows-International-Core-WinPE" 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"> #if $distro_name in ( 'Win7_RU-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )             <InputLocale>0409:00000409;0419:00000419</InputLocale>             <SystemLocale>ru-RU</SystemLocale>             <UILanguage>ru-RU</UILanguage>             <UILanguageFallback>ru-RU</UILanguageFallback>             <UserLocale>ru-RU</UserLocale> #else             <InputLocale>0409:00000409</InputLocale>             <SystemLocale>en-US</SystemLocale>             <UILanguage>en-US</UILanguage>             <UILanguageFallback>en-US</UILanguageFallback>             <UserLocale>en-US</UserLocale> #end if         </component>         <component name="Microsoft-Windows-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">             <DiskConfiguration>                 <WillShowUI>OnError</WillShowUI>                 <Disk wcm:action="add">                     <CreatePartitions>                         <CreatePartition wcm:action="add">                             <Order>1</Order>                             <Extend>true</Extend>                             <Type>Primary</Type>                         </CreatePartition>                     </CreatePartitions>                     <DiskID>0</DiskID>                     <WillWipeDisk>true</WillWipeDisk>                 </Disk>             </DiskConfiguration>             <ImageInstall>                 <OSImage>                     <InstallFrom>                         <Credentials>                             <Domain></Domain>                         </Credentials>                         <MetaData wcm:action="add">                             <Key>/IMAGE/NAME</Key> #if $profile_name == 'IOS'                             <Value>Windows8_VmVare_MacOS_xCode</Value> #else if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64' )                             <Value>Windows 7 PROFESSIONAL</Value> #else if $distro_name in (  'Win2k8-Server_EN-x64' )                             <Value>Windows Server 2008 R2 SERVERENTERPRISE</Value> #else if $distro_name in (  'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )                             <Value>Windows Server 2012 R2 SERVERDATACENTER</Value> #else if $distro_name in (  'Win8_RU-x64' )                             <Value>Windows 8.1 Pro</Value> #else if $distro_name in (  'Win8_EN-x64' )                             <Value>Windows 8.1 Enterprise</Value> #end if                         </MetaData>                     </InstallFrom>                     <InstallTo>                         <DiskID>0</DiskID>                         <PartitionID>1</PartitionID>                     </InstallTo>                 </OSImage>             </ImageInstall>             <UserData>                 <ProductKey> #if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )                     <Key>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</Key> #end if                     <WillShowUI>Never</WillShowUI>                 </ProductKey>                 <AcceptEula>true</AcceptEula>                 <FullName>User</FullName>                 <Organization>My Organization</Organization>             </UserData>             <EnableFirewall>false</EnableFirewall>         </component>         <component name="Microsoft-Windows-PnpCustomizationsWinPE" 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">             <DriverPaths> #if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )                 <PathAndCredentials wcm:action="add" wcm:keyValue="1">                     <Path>\\@@http_server@@\WINOS\Drivers\CHIPSET\Win8</Path>                 </PathAndCredentials> #else                 <PathAndCredentials wcm:action="add" wcm:keyValue="1">                     <Path>\\@@http_server@@\WINOS\Drivers\CHIPSET\5520\Vista</Path>                 </PathAndCredentials>                 <PathAndCredentials wcm:action="add" wcm:keyValue="2">                     <Path>\\@@http_server@@\WINOS\Drivers\CHIPSET\C200\WIN7</Path>                 </PathAndCredentials> #end if                 <PathAndCredentials wcm:action="add" wcm:keyValue="3"> #if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )                     <Path>\\@@http_server@@\WINOS\Drivers\NIC\Win8</Path> #else                     <Path>\\@@http_server@@\WINOS\Drivers\NIC</Path> #end if                 </PathAndCredentials> #if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64', 'Win2k8-Server_EN-x64' )                 <PathAndCredentials wcm:action="add" wcm:keyValue="4">                     <Path>\\@@http_server@@\WINOS\Drivers\ACPI\64\WIN7</Path>                 </PathAndCredentials> #end if                 <PathAndCredentials wcm:action="add" wcm:keyValue="5">                     <Path>\\@@http_server@@\WINOS\Drivers\Storage\64</Path>                 </PathAndCredentials>                 <PathAndCredentials wcm:action="add" wcm:keyValue="6"> #if $distro_name in ( 'Win8_RU-x64' )                     <Path>\\@@http_server@@\WINOS\Drivers\Virt\Win8</Path> #else if $distro_name in (  'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )                     <Path>\\@@http_server@@\WINOS\Drivers\Virt\2012</Path> #else if $distro_name in ( 'Win2k8-Server_EN-x64' )                     <Path>\\@@http_server@@\WINOS\Drivers\Virt\2008</Path> #else                     <Path>\\@@http_server@@\WINOS\Drivers\Virt\Win7</Path> #end if                 </PathAndCredentials>             </DriverPaths>         </component>     </settings>     <settings pass="offlineServicing">         <component name="Microsoft-Windows-LUA-Settings" 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">             <EnableLUA>false</EnableLUA>         </component>     </settings>     <settings pass="specialize">         <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>             <RegisteredOrganization>My Organization</RegisteredOrganization>             <RegisteredOwner>Instructor</RegisteredOwner> #if $distro_name == 'Win7_RU-x64'             <ProductKey>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</ProductKey> #else if $distro_name == 'Win7_EN-x64'             <ProductKey>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</ProductKey> #else if $distro_name == 'Win2k8-Server_EN-x64'             <ProductKey>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</ProductKey> #else if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )             <ProductKey>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</ProductKey> #end if         </component>         <component name="Microsoft-Windows-UnattendedJoin" 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">             <Identification>                 <JoinWorkgroup>WORKGROUP</JoinWorkgroup>             </Identification>         </component>         <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" 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">             <fDenyTSConnections>false</fDenyTSConnections>         </component>         <component name="Networking-MPSSVC-Svc" 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">             <FirewallGroups>                 <FirewallGroup wcm:action="add" wcm:keyValue="EnableRemoteDesktop">                     <Active>true</Active>                     <Group>Remote Desktop</Group>                     <Profile>all</Profile>                 </FirewallGroup>             </FirewallGroups>         </component>         <component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" 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">             <UserAuthentication>0</UserAuthentication>         </component>     </settings>     <settings pass="oobeSystem">         <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">             <OOBE>                 <ProtectYourPC>3</ProtectYourPC>                 <NetworkLocation>Work</NetworkLocation>             </OOBE>             <UserAccounts>                 <AdministratorPassword>                     <Value>XXXX</Value>                     <PlainText>false</PlainText>                 </AdministratorPassword>                 <LocalAccounts>                     <LocalAccount wcm:action="add">                         <Password>                             <Value>XXXXX</Value>                             <PlainText>false</PlainText>                         </Password>                         <Name>User</Name>                         <Group>Administrators</Group>                     </LocalAccount>                 </LocalAccounts>                 <DomainAccounts>                     <DomainAccountList wcm:action="add">                         <Domain>WORKGOUP</Domain>                         <DomainAccount wcm:action="add">                             <Name>Domain Admins</Name>                             <Group>Administrators</Group>                         </DomainAccount>                         <DomainAccount wcm:action="add">                             <Name>User</Name>                             <Group>Administrators</Group>                         </DomainAccount>                     </DomainAccountList>                 </DomainAccounts>             </UserAccounts>             <TimeZone>Central Asia Standard Time</TimeZone>             <RegisteredOrganization>My Organization</RegisteredOrganization>             <RegisteredOwner>User</RegisteredOwner>             <FirstLogonCommands>                 <SynchronousCommand wcm:action="add">                     <RequiresUserInput>false</RequiresUserInput>                     <Order>1</Order>                     <CommandLine>cmd /C wmic useraccount where "name='user'" set PasswordExpires=FALSE</CommandLine>                 </SynchronousCommand>                 <SynchronousCommand wcm:action="add">                     <RequiresUserInput>false</RequiresUserInput>                     <Order>2</Order>                     <CommandLine>c:\post_install.cmd @@profile_name@@</CommandLine>                 </SynchronousCommand>             </FirstLogonCommands>             <AutoLogon>                 <Password>                     <Value>XXXX</Value>                     <PlainText>false</PlainText>                 </Password>                 <Enabled>true</Enabled>                 <Domain>WORKGOUP</Domain>                 <Username>User</Username>                 <LogonCount>10000</LogonCount>             </AutoLogon>         </component>     </settings> #if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64' )     <cpi:offlineImage cpi:source="catalog:d:/sources/install_windows 7 professional.clg" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> #else if $distro_name in ( 'Win2k8-Server_EN-x64' )     <cpi:offlineImage cpi:source="catalog:d:/sources/install_windows server 2008 r2 serverenterprise.clg" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> #else if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )     <cpi:offlineImage cpi:source="wim:c:/netboot/install.wim#Windows Server 2012 R2 SERVERDATACENTER" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> #else if $distro_name in ( 'Win8_RU-x64' )     <cpi:offlineImage cpi:source="catalog:d:/sources/install_windows 8.1 Pro.clg" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> #end if </unattend> #end if 

Ключевой момент здесь в строках, при помощи которых стартует скрипт с именем профиля в качестве параметра:

  • для Xp, 2003
     [GuiRunOnce] "%Systemdrive%\post_install.cmd @@profile_name@@" 
  • для 7, 8, 2008, 2012
                    <SynchronousCommand wcm:action="add">                     <RequiresUserInput>false</RequiresUserInput>                     <Order>2</Order>                     <CommandLine>c:\post_install.cmd @@profile_name@@</CommandLine>                 </SynchronousCommand> 

Шаблон startnet.template

Шаблон на основе которого формируются стартовые скрипты для wim образов.

  • После инициализации сетевого стека скрипт находит IP адрес DHCP сервера (там же у нас находится и cobbler с samba).
  • Монтируются необходимые сетевые ресурсы samba.
  • Из метаданных cobbler извлекается имя файла ответов ($kernel_options[«sif»]) для данного профиля.
  • Запросом к DNS серверу находим имя инсталлируемого компьютера в DNS и заносим его в файл ответов.
  • Запускаем инсталляцию с этим файлом ответов.

Содержимое файла /var/lib/cobbler/kickstarts/startnet.template

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"=="            <ComputerName>*</ComputerName>" (      echo             ^<ComputerName^>%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 определения домена строки:

    <hostdev mode='capabilities' type='misc'>       <source>         <char>/dev/fuse</char>      </source>     </hostdev> 

Создаем определения дистрибутивов и профилей

Создаем определение дистрибутивов в cobbler:

 # systemctl restart cobblerd # cobbler distro add --name=WinXp_RU-i386 --kernel=/var/lib/tftpboot/winos/WinXp_RU-i386/pxeboot.n12 --initrd=/var/lib/tftpboot/winos/add_ram.dat --arch=i386 --breed=windows --os-version=XP --kopts='post_install=/var/lib/tftpboot/winos/WinXp_RU-i386/$OEM$/$1/post_install.cmd'  # cobbler distro add --name=Win7_RU-x64 --kernel=/var/lib/tftpboot/winos/Win7_RU-x64/pxeboot.n12 --initrd=/var/lib/tftpboot/winos/add_ram.dat --arch=x86_64 --breed=windows --os-version=7 --kopts='post_install=/var/lib/tftpboot/winos/Win7_RU-x64/sources/$OEM$/$1/post_install.cmd'  # cobbler distro add --name=Win8_RU-x64 --kernel=/var/lib/tftpboot/winos/Win8_RU-x64/pxeboot.n12 --initrd=/var/lib/tftpboot/winos/add_ram.dat --arch=x86_64 --breed=windows --os-version=8 --kopts='post_install=/var/lib/tftpboot/winos/Win8_RU-x64/sources/$OEM$/$1/post_install.cmd'  и т.д. 

Здесь kernel фиктивный — PXE будет загружать файл созданный на основе этого pxeboot.n12 по команде cobbler sync.
/var/lib/tftpboot/winos/add_ram.dat тоже фиктивный initrd, в смысле можно подсунуть любой— иначе distro не создать.

Создаем определение профилей в cobbler:

 # cobbler profile add --name=WinXp_RU-i386 --distro=WinXp_RU-i386 --kickstart=/var/lib/cobbler/kickstarts/win.ks --kopts='pxeboot=winr0.0,bootmgr=xplr0,sif=winr0.sif'  # cobbler profile add --name=Win7_RU-x64 --distro=Win7_RU-x64 --kickstart=/var/lib/cobbler/kickstarts/win.ks --kopts='pxeboot=win7ra.0,bootmgr=boot7ra.exe,bcd=7Ra,winpe=winpe.wim,sif=autounattended.xml'  # cobbler profile add --name=Win8_RU-x64 --distro=Win8_RU-x64 --kickstart=/var/lib/cobbler/kickstarts/win.ks --kopts='pxeboot=win81a.0,bootmgr=boot81a.exe,bcd=B8a,winpe=winpe.wim,sif=autounattended.xml' 

Создадим профили для двух тестовых вариантов инсталляции на основе дистрибутива Win8_RU-x64.

 # cobbler profile add --name=Win8-test1 --distro=Win8_RU-x64 --kickstart=/var/lib/cobbler/kickstarts/win.ks --kopts='pxeboot=win81b.0,bootmgr=boot81b.exe,bcd=B8b,winpe=winpb.wim,sif=autounattended01.xml'  # cobbler profile add --name=Win8-test2 --distro=Win8_RU-x64 --kickstart=/var/lib/cobbler/kickstarts/win.ks --kopts='pxeboot=win81c.0,bootmgr=boot81c.exe,bcd=B8c,winpe=winpc.wim,sif=autounattended02.xml' 

Теперь в win.ks можно вставлять конструкции типа:

 #if $profile_name == ' Win8-test1' <code> #end if #if $profile_name == ' Win8-test2' <code> #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:

Содержимое файла /usr/lib/python2.7/site-packages/cobbler/modules/sync_post_wingen.py

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.

Выполняем команды:

 # mkdir /mnt/wim # systemctl restart  cobblerd # cobbler sync # systemctl restart xinetd 

Теперь создаем виртуалку с инсталляцией по сети, либо на обычном компьютере жмем кнопки Reset, F12 (ну или что у вас в BIOS для этих целей прописано), выбираем нужный пункт в меню и наслаждаемся автоматической сетевой инсталляцией как Linux так и Windows.

При необходимости внести изменения в сценарий пользуемся условной генерацией кода в win.ks. У меня в шапке этого файла сначала идет общая часть нужная при любом варианте инсталляции, в потом длинный if/else:

 #if $profile_name == '<profile_name1>' <code> #elseif $profile_name == '<profile_name2>' <code> . . . #elseif $profile_name == '<profile_nameN>' <code> #end if 

Дублирующейся, либо логически замкнутый код вынесен в снипеты.

Чего не хватает

  1. Не будет работать импорт Windows дистрибутива. Новые версии Microsoft выпускает не так уж и часто, так что это можно пережить.
  2. Не будет работать (даже не пытался проверять) создание ISO образа Windows дистрибутива.
  3. Автоматическая генерация PXE меню в cobbler в случае Windows профилей будет работать неправильно.
  4. Если не соблюдать правила формирования имен загрузочных файлов, придется изменять /etc/tftpd.rules. А при совпадении имен загрузочных файлов в рамках одного дистрибутива, файлы одного профиля могут перезаписаться файлами другого профиля.

ссылка на оригинал статьи http://habrahabr.ru/post/263883/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *