Created
November 11, 2025 18:35
-
-
Save ianballou/9e38948de7b4e9064e6777cacfccd9a4 to your computer and use it in GitHub Desktop.
Default Foreman kickstart provisioning template for image mode machines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <%# | |
| kind: provision | |
| name: Kickstart Default bootc | |
| model: ProvisioningTemplate | |
| oses: | |
| - AlmaLinux | |
| - CentOS | |
| - CentOS_Stream | |
| - Fedora | |
| - RedHat | |
| - Rocky | |
| description: | | |
| Provisioning template for kickstart based OSTree/Bootc container installations. | |
| The output is fetched by Anaconda installer during the network based installation. | |
| This template accepts the following parameters: | |
| - ostreecontainer: string (required) - The container image URL (e.g., quay.io/exampleos/foo:latest) | |
| - lang: string (default="en_US.UTF-8") | |
| - selinux-mode: string (default="enforcing") | |
| - keyboard: string (default="us") | |
| - time-zone: string (default="UTC") | |
| - http-proxy: string (default="") | |
| - http-proxy-port: string (default="") | |
| - ntp-pools: array (default=undef) | |
| - ntp-server: string (default=undef) | |
| - bootloader-append: string (default="nofb quiet splash=quiet") | |
| - disable-firewall: boolean (default=false) | |
| - kdump-options: string (default=undef) | |
| - use_graphical_installer: boolean (default=false) | |
| Reference links: | |
| - https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/installing_rhel_using_an_image/kickstart-commands-and-options-reference_installing-rhel-using-an-image | |
| -%> | |
| <% | |
| rhel_compatible = @host.operatingsystem.family == 'Redhat' && @host.operatingsystem.name != 'Fedora' | |
| is_fedora = @host.operatingsystem.name == 'Fedora' | |
| os_major = @host.operatingsystem.major.to_i | |
| os_minor = @host.operatingsystem.minor.to_i | |
| realm_compatible = (@host.operatingsystem.name == 'Fedora' && os_major >= 20) || (rhel_compatible && os_major >= 7) | |
| # safemode renderer does not support unary negation | |
| proxy_uri = host_param('http-proxy') ? "http://#{host_param('http-proxy')}:#{host_param('http-proxy-port')}" : nil | |
| proxy_string = proxy_uri ? " --proxy=#{proxy_uri}" : '' | |
| section_end = (rhel_compatible && os_major <= 5) ? '' : '%end' | |
| iface = @host.provision_interface | |
| use_rhsm = (@host.operatingsystem.name == 'RedHat' || @host.operatingsystem.name == 'RHEL') && os_major >= 9 | |
| ostreecontainer = host_param('ostreecontainer') | |
| unless ostreecontainer.present? | |
| raise "Host parameter 'ostreecontainer' is required for this template." | |
| end | |
| -%> | |
| # This kickstart file was rendered from the Foreman provisioning template "<%= @template_name %>". | |
| # for <%= @host %> running <%= @host.operatingsystem.name %> <%= os_major %> <%= @arch %> | |
| # Organization: <%= @host.organization %> | |
| # Location: <%= @host.location %> | |
| <% | |
| if plugin_present?('katello') | |
| -%> | |
| # Lifecycle environment: <%= @host.single_lifecycle_environment %> | |
| # Content View: <%= @host.single_content_view %> | |
| # Content Source: <%= @host.content_source %> | |
| <% end -%> | |
| <% if (is_fedora && os_major < 29) || (rhel_compatible && os_major <= 7) -%> | |
| install | |
| <% end -%> | |
| lang <%= host_param('lang') || 'en_US.UTF-8' %> | |
| selinux --<%= host_param('selinux-mode') || host_param('selinux') || 'enforcing' %> | |
| keyboard <%= host_param('keyboard') || 'us' %> | |
| <% | |
| # start with provisioning interface, then other non-bond non-bridge interfaces and the bonds + bridges at the end | |
| @host.interfaces.reject{ |iface| iface.bmc? }.sort_by { |iface| (iface.bond? || iface.bridge?) ? 0 : iface.provision? ? 20 : 10 }.each do |iface| | |
| -%> | |
| <%= snippet( | |
| 'kickstart_network_interface', | |
| variables: { | |
| iface: iface, | |
| host: @host, | |
| static: @static, | |
| static6: @static6 | |
| } | |
| ) -%> | |
| <% | |
| end | |
| -%> | |
| ostreecontainer --url <%= ostreecontainer %> <%= proxy_string %> | |
| rootpw --iscrypted <%= root_pass %> | |
| #<%# Firewall -%> | |
| #<% if host_param_true?('disable-firewall') -%> | |
| #firewall --disable | |
| #<% else -%> | |
| #firewall --<%= os_major >= 6 ? 'service=' : '' %>ssh | |
| #<% end -%> | |
| #<%# Kdump -%> | |
| #<% if host_param('kdump-options').present? -%> | |
| #%addon com_redhat_kdump <%= host_param('kdump-options') %> | |
| #%end | |
| #<% end -%> | |
| <%# Timezone + NTP -%> | |
| <% if rhel_compatible && os_major < 9 -%> | |
| timezone --utc <%= host_param('time-zone') || 'UTC' %> <%= host_param('ntp-server') ? "--ntpservers #{host_param('ntp-server')}" : '' %> | |
| <% else -%> | |
| timezone --utc <%= host_param('time-zone') || 'UTC' %> | |
| <% if host_param('ntp-pools') -%> | |
| <% host_param('ntp-pools').each do |ntppool| -%> | |
| timesource --ntp-pool <%= ntppool %> | |
| <% end -%> | |
| <% elsif host_param('ntp-server') -%> | |
| timesource --ntp-server <%= host_param('ntp-server') %> | |
| <% end -%> | |
| <% end -%> | |
| <% if rhel_compatible -%> | |
| services --disabled gpm,sendmail,cups,pcmcia,isdn,rawdevices,hpoj,bluetooth,openibd,avahi-daemon,avahi-dnsconfd,hidd,hplip,pcscd | |
| <% end -%> | |
| <% if realm_compatible && @host.realm && @host.realm.realm_type == 'Active Directory' -%> | |
| # One-time password will be requested at install time. Otherwise, $HOST[OTP] is used as a placeholder value. | |
| realm join --one-time-password='<%= @host.otp || "$HOST[OTP]" %>' <%= @host.realm %> | |
| <% end -%> | |
| <% if @host.operatingsystem.name == 'Fedora' && os_major <= 16 -%> | |
| # Bootloader exception for Fedora 16: | |
| bootloader --append="<%= host_param('bootloader-append') || 'nofb quiet splash=quiet' %> <%= ks_console %>" <%= grub_pass %> | |
| part biosboot --fstype=biosboot --size=1 | |
| <% else -%> | |
| bootloader --location=mbr --append="<%= host_param('bootloader-append') || 'nofb quiet splash=quiet' %>" <%= grub_pass %> | |
| <% if os_major == 5 -%> | |
| key --skip | |
| <% end -%> | |
| <% end -%> | |
| <% if @dynamic -%> | |
| %include /tmp/diskpart.cfg | |
| <% else -%> | |
| <%= @host.diskLayout %> | |
| <% end -%> | |
| <%= snippet('kickstart_rhsm') if use_rhsm -%> | |
| <% if host_param_true?('use_graphical_installer') -%> | |
| graphical | |
| <% else -%> | |
| skipx | |
| text | |
| <% end -%> | |
| reboot<% if host_param_true?('install_reboot_kexec') %> --kexec<% end %> | |
| <% if @dynamic -%> | |
| %pre --log=/tmp/install.pre.dynamic.log | |
| <%= snippet_if_exists(template_name + " custom pre") %> | |
| <%= @host.diskLayout %> | |
| <%= section_end %> | |
| <% end -%> | |
| %post --nochroot | |
| exec < /dev/tty3 > /dev/tty3 | |
| chvt 3 | |
| ( | |
| <% if host_param_false?('no-resolv-override') -%> | |
| cp -va /etc/resolv.conf /mnt/sysimage/etc/resolv.conf | |
| <% end -%> | |
| <%= snippet_if_exists(template_name + " custom postnochroot") -%> | |
| chvt 1 | |
| ) 2>&1 | tee /mnt/sysimage/root/install.postnochroot.log | |
| <%= section_end %> | |
| <%# | |
| Main post script, if it fails the last post is still executed. | |
| %> | |
| %post | |
| exec < /dev/tty3 > /dev/tty3 | |
| chvt 3 | |
| ( | |
| logger "Starting anaconda <%= @host %> postinstall" | |
| <%= snippet('remote_execution_ssh_keys') %> | |
| <%= snippet "blacklist_kernel_modules" %> | |
| <%= snippet('ansible_provisioning_callback') %> | |
| <%= snippet 'efibootmgr_netboot' %> | |
| <%= snippet_if_exists(template_name + " custom post") %> | |
| touch /tmp/foreman_built | |
| <% if host_param_true?('use_graphical_installer') -%> | |
| chvt 6 | |
| <% else -%> | |
| chvt 1 | |
| <% end -%> | |
| ) 2>&1 | tee /root/install.post.log | |
| <%= section_end %> | |
| # copy %pre log files into chroot | |
| %post --nochroot | |
| cp -vf /tmp/*.pre.*.log /mnt/sysimage/root/ | |
| <%= section_end %> | |
| <%# | |
| The last post section halts Anaconda to prevent endless loop in case HTTP request fails | |
| %> | |
| <% if (is_fedora && os_major < 20) || (rhel_compatible && os_major < 7) -%> | |
| %post | |
| <% else -%> | |
| %post --erroronfail --log=/root/install-callhome.post.log | |
| <% end -%> | |
| <%= snippet 'eject_cdrom' -%> | |
| if test -f /tmp/foreman_built; then | |
| echo "calling home: build is done!" | |
| <%= indent(2, skip1: true, skip_content: 'EOF') { snippet('built', :variables => { :endpoint => 'built', :method => 'POST', :body_file => '/root/install.post.log' }) } -%> | |
| else | |
| echo "calling home: build failed!" | |
| <%= indent(2, skip1: true, skip_content: 'EOF') { snippet('built', :variables => { :endpoint => 'failed', :method => 'POST', :body_file => '/root/install.post.log' }) } -%> | |
| fi | |
| sync | |
| <%= section_end %> |
Author
Author
The following template is working with subscription-manager registration. The key here was that DNS wasn't set up correctly in the system environment:
<%#
kind: provision
name: Kickstart Default bootc - Trimmed
model: ProvisioningTemplate
oses:
- AlmaLinux
- CentOS
- CentOS_Stream
- Fedora
- RedHat
- Rocky
-%>
<%
# Variable setup for post scripts and container URL
ostreecontainer = host_param('ostreecontainer')
-%>
# This kickstart file was rendered from the Foreman provisioning template "<%= @template_name %>".
lang <%= host_param('lang') || 'en_US.UTF-8' %>
selinux --<%= host_param('selinux-mode') || 'enforcing' %>
keyboard <%= host_param('keyboard') || 'us' %>
<%
# Network setup is essential for pulling the container and reporting to Foreman
@host.interfaces.reject{ |iface| iface.bmc? }.sort_by { |iface| (iface.bond? || iface.bridge?) ? 0 : iface.provision? ? 20 : 10 }.each do |iface|
-%>
<%= snippet(
'kickstart_network_interface',
variables: {
iface: iface,
host: @host,
static: @static,
static6: @static6
}
) -%>
<%
end
-%>
# --- Core Installation ---
# 1. Set the container image as the installation source
ostreecontainer --url <%= ostreecontainer %>
# 2. Set the root password to make the system login-able
rootpw --iscrypted <%= root_pass %>
# 3. Allow SSH for remote login
firewall --service=ssh
# --- Time ---
timezone --utc <%= host_param('time-zone') || 'UTC' %>
<% if host_param('ntp-pools') -%>
<% host_param('ntp-pools').each do |ntppool| -%>
timesource --ntp-pool <%= ntppool %>
<% end -%>
<% elsif host_param('ntp-server') -%>
timesource --ntp-server <%= host_param('ntp-server') %>
<% end -%>
# --- Bootloader and Partitioning ---
# This assumes you are assigning a partition table in Foreman.
# For a modern bootc install, you might replace the bootloader/diskLayout
# sections with: autopart --type=bootc
bootloader --location=mbr --append="<%= host_param('bootloader-append') || 'nofb quiet splash=quiet' %>" <%= grub_pass %>
<%= @host.diskLayout %>
# --- Finalize ---
text
skipx
reboot
# --- Post-Install Scripts ---
%post --nochroot
exec < /dev/tty3 > /dev/tty3
chvt 3
(
<% if host_param_false?('no-resolv-override') -%>
cp -va /etc/resolv.conf /mnt/sysimage/etc/resolv.conf
<% end -%>
# Hack to try fixing DNS
echo "search example.com" > /mnt/sysimage/etc/resolv.conf
echo "nameserver 192.168.73.1" >> /mnt/sysimage/etc/resolv.conf
chvt 1
) 2>&1 | tee /mnt/sysimage/root/install.postnochroot.log
%end
<%#
This section injects SSH keys for Foreman, registers the system,
and signals the build is done.
%>
%post
exec < /dev/tty3 > /dev/tty3
chvt 3
(
# Ensure DNS resolution works in chroot environment
cat > /etc/resolv.conf << 'EOF'
search example.com
nameserver 192.168.73.1
EOF
echo "=== DNS Configuration ==="
cat /etc/resolv.conf
echo "=== Testing DNS Resolution ==="
nslookup cdn.redhat.com || echo "WARNING: Cannot resolve cdn.redhat.com"
echo "=========================="
<%= snippet 'redhat_register' -%>
<%= snippet('remote_execution_ssh_keys') %>
touch /tmp/foreman_built
chvt 1
) 2>&1 | tee /root/install.post.log
%end
<%#
The last post section tells Foreman the build is complete.
%>
%post --erroronfail --log=/root/install-callhome.post.log
if test -f /tmp/foreman_built; then
echo "calling home: build is done!"
<%= indent(2, skip1: true, skip_content: 'EOF') { snippet('built', :variables => { :endpoint => 'built', :method => 'POST', :body_body_file => '/root/install.post.log' }) } -%>
else
echo "calling home: build failed!"
<%= indent(2, skip1: true, skip_content: 'EOF') { snippet('built', :variables => { :endpoint => 'failed', :method => 'POST', :body_body_file => '/root/install.post.log' }) } -%>
fi
sync
%end
Author
Latest KS template with /etc/resolv in chroot from the Foreman host:
<%#
kind: provision
name: Kickstart Default bootc - Trimmed
model: ProvisioningTemplate
oses:
- AlmaLinux
- CentOS
- CentOS_Stream
- Fedora
- RedHat
- Rocky
-%>
<%
# Variable setup for post scripts and container URL
ostreecontainer = host_param('ostreecontainer')
-%>
# This kickstart file was rendered from the Foreman provisioning template "<%= @template_name %>".
lang <%= host_param('lang') || 'en_US.UTF-8' %>
selinux --<%= host_param('selinux-mode') || 'enforcing' %>
keyboard <%= host_param('keyboard') || 'us' %>
<%
# Network setup is essential for pulling the container and reporting to Foreman
@host.interfaces.reject{ |iface| iface.bmc? }.sort_by { |iface| (iface.bond? || iface.bridge?) ? 0 : iface.provision? ? 20 : 10 }.each do |iface|
-%>
<%= snippet(
'kickstart_network_interface',
variables: {
iface: iface,
host: @host,
static: @static,
static6: @static6
}
) -%>
<%
end
-%>
# --- Core Installation ---
# 1. Set the container image as the installation source
ostreecontainer --url <%= ostreecontainer %>
# 2. Set the root password to make the system login-able
rootpw --iscrypted <%= root_pass %>
# 3. Allow SSH for remote login
firewall --service=ssh
# --- Time ---
timezone --utc <%= host_param('time-zone') || 'UTC' %>
<% if host_param('ntp-pools') -%>
<% host_param('ntp-pools').each do |ntppool| -%>
timesource --ntp-pool <%= ntppool %>
<% end -%>
<% elsif host_param('ntp-server') -%>
timesource --ntp-server <%= host_param('ntp-server') %>
<% end -%>
# --- Bootloader and Partitioning ---
# This assumes you are assigning a partition table in Foreman.
# For a modern bootc install, you might replace the bootloader/diskLayout
# sections with: autopart --type=bootc
bootloader --location=mbr --append="<%= host_param('bootloader-append') || 'nofb quiet splash=quiet' %>" <%= grub_pass %>
<%= @host.diskLayout %>
# --- Finalize ---
text
skipx
reboot
# --- Post-Install Scripts ---
%post --nochroot
exec < /dev/tty3 > /dev/tty3
chvt 3
(
<% if host_param_false?('no-resolv-override') -%>
# Copy DNS configuration from installation environment
cp -va /etc/resolv.conf /mnt/sysimage/etc/resolv.conf
<% end -%>
chvt 1
) 2>&1 | tee /mnt/sysimage/root/install.postnochroot.log
%end
<%#
This section injects SSH keys for Foreman, registers the system,
and signals the build is done.
%>
%post
exec < /dev/tty3 > /dev/tty4
chvt 3
(
# DNS should already be configured from %post --nochroot copy
# But verify and recreate if missing (bootc containers may not have it)
if [ ! -f /etc/resolv.conf ] || [ ! -s /etc/resolv.conf ]; then
echo "WARNING: /etc/resolv.conf missing or empty in chroot, recreating from Foreman configuration"
cat > /etc/resolv.conf << 'RESOLV_EOF'
<% if @host.domain -%>
search <%= @host.domain.name %>
<% end -%>
<% [@host.subnet.dns_primary, @host.subnet.dns_secondary].compact.each do |nameserver| -%>
nameserver <%= nameserver %>
<% end -%>
RESOLV_EOF
fi
echo "=== DNS Configuration ==="
cat /etc/resolv.conf
echo "=== Testing DNS Resolution ==="
nslookup cdn.redhat.com || echo "WARNING: Cannot resolve cdn.redhat.com"
echo "=========================="
<%= snippet 'redhat_register' -%>
<%= snippet('remote_execution_ssh_keys') %>
touch /tmp/foreman_built
chvt 1
) 2>&1 | tee /root/install.post.log
%end
<%#
The last post section tells Foreman the build is complete.
%>
%post --erroronfail --log=/root/install-callhome.post.log
if test -f /tmp/foreman_built; then
echo "calling home: build is done!"
<%= indent(2, skip1: true, skip_content: 'EOF') { snippet('built', :variables => { :endpoint => 'built', :method => 'POST', :body_body_file => '/root/install.post.log' }) } -%>
else
echo "calling home: build failed!"
<%= indent(2, skip1: true, skip_content: 'EOF') { snippet('built', :variables => { :endpoint => 'failed', :method => 'POST', :body_body_file => '/root/install.post.log' }) } -%>
fi
sync
%end
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is a shortened version of the KS template. The firewall setting is back because I built firewalld into my container image (quay.io/iballou/rhel10-squid) and pushed it up. Note that it is still missing subscription registration - for some reason I keep hitting an error:
[Errno -2] Name or service not known.