Mantle 2.0 Templates Library
Overview
This page provides reference templates that can be used when building or customizing deployment workflows in Mantle 2.0. Each example is presented with a brief summary, implementation notes, and direct links to the source files so you can download, modify, and upload them to Mantle Assets.
Available Templates
Red Hat Enterprise Linux Kickstart
The RHEL Kickstart template is intended for automated Red Hat Enterprise Linux provisioning in PXE-driven or unattended installation workflows. It includes pre-install disk and RAID cleanup logic, static management network configuration, root and user account handling through template variables, LVM-based partitioning, post-install service enablement, and package selection for virtualization-capable hosts.
Full file: rhel-kickstart.j2
Variants: Minimal | Server | Server with GUI | Virtualization Host
What this template does
- Clears existing RAID and volume group metadata before installation.
- Wipes detected installation disks to reduce conflicts from previous deployments.
- Configures a static management interface using templated values.
- Supports templated root and standard user password handling.
- Creates a UEFI-compatible disk layout with LVM-backed logical volumes.
- Enables Cockpit and SSH after installation.
- Updates SSH settings to allow password-based root access.
- Prepares the host for virtualization workloads.
Key implementation notes
- Uses Jinja-style variables (for example,
{{ ip }},{{ hostname }},{{ mgmt_interface|default("eth0") }}) and is meant to be rendered before use. - The install target is driven by
{{ install_device }}, and Mantle binds that value for each host entry when it renders the kickstart file. - The default LVM layout consumes roughly 720 GB before
--growallocations, so adjust thelogvolsizes to satisfy your operational and security requirements. - Two
%postsections provide the same logic. If duplication is not required in your workflow, you can consolidate them before uploading. - SELinux is set to permissive and SSH password logins for root are enabled. Review those settings for production-facing environments.
Kickstart template requirements
Provide the data points below so the Enterprise Linux PXE workflow can render a valid ks file for every host.
Asset references
platform: set toenterprise_linuxso Mantle runs the Linux build path.platform_ver: Enterprise Linux ISO asset ID used for PXE boot/repo paths.kickstart_ver: kickstart asset ID; Mantle validates it against the ISO's major version.
PXE and management networking
pxe_server: HTTP/TFTP endpoint used by%pre, PXELINUX, and GRUB.netmask: subnet mask applied in the kickstartnetwork --bootproto=staticstanza.gateway: default route for the management interface during installation.dns_servers[0]: first DNS server passed via--nameserver; add additional entries as needed.
Per-host variables
Mantle renders the kickstart once per host and injects each entry's fields as top-level variables (for example, {{ ip }} and {{ install_device }}).
ip: static IP each host should assume while installing.hostname: hostname injected into the kickstart network stanza.username: username for the non-root admin account created post-install.pw: password for that account; Mantle hashes it before injectingpwd.mgmt_interface: interface name used in thenetworkcommand and post-installnmcli.install_device: limitsignoredisk/bootloader targets; required when multiple disks exist.
Optional but recommended
serial_console: device and baud pair for GRUB/UEFI console redirection.extra_kernel_args: site-specific kernel parameters appended to the installer command line.
Build-level prerequisites
build_interface: interface Mantle uses during provisioning; required to pass pre-flight checks.bios_devices: either one EL8000 entry or a list matching the host count; enforces BIOS automation alignment.
When to use it
Use this template when you need a repeatable RHEL host build process that supports unattended deployment, standardized partitioning, and post-install virtualization readiness.
Minimal
Download: rhel-kickstart-minimal.j2 Quick reference copy
%pre
wget http://{{ pxe_server }}/started || true
udevadm settle
dmsetup remove_all
# De-activate any exiting Volume Groups
vgchange -an system
vgchange -an os
# Clear software raid devices if any
raid_devices=$(mktemp /tmp/mdstat.XXXXXXXXX)
grep '^md' /proc/mdstat | cut -d: -f1 > "$raid_devices"
if [ -s "$raid_devices" ]; then
for raid in $(cat "$raid_devices"); do
wipefs -f -a "/dev/$raid"
mdadm --stop -f "/dev/$raid"
if [ $? != "0" ]; then
udevadm settle
dmsetup remove_all
mdadm --stop -f "/dev/$raid"
fi
done
else
echo "All RAID devices are cleared"
fi
rm -vf "$raid_devices"
# Wipe every detected block device that looks like an install target
install_disks=$(lsblk -ndo NAME,TYPE | awk '$2 == "disk" {print "/dev/" $1}')
if [ -z "$install_disks" ]; then
echo "No block devices detected to wipe"
else
for disk in $install_disks; do
echo "Wiping signatures on $disk"
wipefs -f -a "$disk"
done
fi
%end
# Accept Eula
eula --agreed
# Firewall configuration
firewall --enabled --ssh
# Keyboard layouts
keyboard 'us'
# Timezone
timezone Etc/UTC --isUtc
# Network information
network --bootproto=static --device={{ mgmt_interface|default("eth0") }} --gateway={{ gateway }} --ip={{ ip }} --netmask={{ netmask }} {% if dns_servers %}--nameserver={{ dns_servers | first }}{% endif %} --hostname={{ hostname }}
# Root PW
{% if pwd is defined and pwd %}
rootpw --iscrypted {{ pwd }}
{% elif pw is defined and pw %}
rootpw {{ pw }}
{% else %}
# rootpw not set
{% endif %}
# Reboot after installation
reboot
# Use text mode install
text
# System language
lang en_US
# SELinux configuration
selinux --permissive
{% if pwd is defined and pwd %}
user --name={{ username }} --groups=wheel --password={{ pwd }} --iscrypted
{% elif pw is defined and pw %}
user --name={{ username }} --groups=wheel --password={{ pw }}
{% else %}
# user password not set
{% endif %}
# Partition clearing information
clearpart --all --initlabel
# Disk partitioning information
## EFI partition (required for UEFI)
part /boot/efi --fstype="efi" --size=600 --fsoptions="umask=0077,shortname=winnt" --ondisk={{ install_device }}
## Boot partition
part /boot --fstype="xfs" --size=1024 --ondisk={{ install_device }}
##Create LVM physical volume
part pv.01 --size=16384 --grow --ondisk={{ install_device }}
## Create volume group
volgroup vg0 pv.01
## Logical volumes
logvol / --vg=vg0 --name=lv_root --size=20480 --fstype=xfs
logvol /home --vg=vg0 --name=lv_home --size=102400 --fstype=xfs
logvol /var --vg=vg0 --name=lv_var --size=500000 --grow --fstype=xfs
logvol /var/log --vg=vg0 --name=lv_var_log --size=20480 --fstype=xfs
logvol /var/log/audit --vg=vg0 --name=lv_var_log_audit --size=20480 --fstype=xfs
logvol /var/tmp --vg=vg0 --name=lv_var_tmp --size=20480 --fstype=xfs
logvol /tmp --vg=vg0 --name=lv_tmp --size=20480 --fstype=xfs
logvol swap --vg=vg0 --name=lv_swap --size=15360 --fstype=swap
%post --log=/root/ks-post.log
systemctl enable --now sshd
# Allow root login with password over SSH
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl restart sshd
nmcli connection modify {{ mgmt_interface|default("eth0") }} ipv6.method ignore
%end
%packages
@^minimal-environment
aide
audit
expect
fapolicyd
firewalld
kexec-tools
opensc
openscap
openscap-scanner
openssh-server
python3
openssl-pkcs11
policycoreutils
postfix
rng-tools
rsyslog
rsyslog-gnutls
scap-security-guide
tmux
usbguard
-abrt
-abrt-addon-ccpp
-abrt-addon-kerneloops
-abrt-cli
-abrt-plugin-sosreport
-iprutils
-krb5-server
-krb5-workstation
-libreport-plugin-logger
-python3-abrt-addon
-rsh-server
-sendmail
-telnet-server
-tftp-server
-tuned
-vsftpd
%end
Server
Download: rhel-kickstart-server.j2 Quick reference copy
%pre
wget http://{{ pxe_server }}/started || true
udevadm settle
dmsetup remove_all
# De-activate any exiting Volume Groups
vgchange -an system
vgchange -an os
# Clear software raid devices if any
raid_devices=$(mktemp /tmp/mdstat.XXXXXXXXX)
grep '^md' /proc/mdstat | cut -d: -f1 > "$raid_devices"
if [ -s "$raid_devices" ]; then
for raid in $(cat "$raid_devices"); do
wipefs -f -a "/dev/$raid"
mdadm --stop -f "/dev/$raid"
if [ $? != "0" ]; then
udevadm settle
dmsetup remove_all
mdadm --stop -f "/dev/$raid"
fi
done
else
echo "All RAID devices are cleared"
fi
rm -vf "$raid_devices"
# Wipe every detected block device that looks like an install target
install_disks=$(lsblk -ndo NAME,TYPE | awk '$2 == "disk" {print "/dev/" $1}')
if [ -z "$install_disks" ]; then
echo "No block devices detected to wipe"
else
for disk in $install_disks; do
echo "Wiping signatures on $disk"
wipefs -f -a "$disk"
done
fi
%end
# Accept Eula
eula --agreed
# Firewall configuration
firewall --enabled --ssh
# Keyboard layouts
keyboard 'us'
# Timezone
timezone Etc/UTC --isUtc
# Network information
network --bootproto=static --device={{ mgmt_interface|default("eth0") }} --gateway={{ gateway }} --ip={{ ip }} --netmask={{ netmask }} {% if dns_servers %}--nameserver={{ dns_servers | first }}{% endif %} --hostname={{ hostname }}
# Root PW
{% if pwd is defined and pwd %}
rootpw --iscrypted {{ pwd }}
{% elif pw is defined and pw %}
rootpw {{ pw }}
{% else %}
# rootpw not set
{% endif %}
# Reboot after installation
reboot
# Use text mode install
text
# System language
lang en_US
# SELinux configuration
selinux --permissive
{% if pwd is defined and pwd %}
user --name={{ username }} --groups=wheel --password={{ pwd }} --iscrypted
{% elif pw is defined and pw %}
user --name={{ username }} --groups=wheel --password={{ pw }}
{% else %}
# user password not set
{% endif %}
# Partition clearing information
clearpart --all --initlabel
# Disk partitioning information
## EFI partition (required for UEFI)
part /boot/efi --fstype="efi" --size=600 --fsoptions="umask=0077,shortname=winnt" --ondisk={{ install_device }}
## Boot partition
part /boot --fstype="xfs" --size=1024 --ondisk={{ install_device }}
##Create LVM physical volume
part pv.01 --size=16384 --grow --ondisk={{ install_device }}
## Create volume group
volgroup vg0 pv.01
## Logical volumes
logvol / --vg=vg0 --name=lv_root --size=20480 --fstype=xfs
logvol /home --vg=vg0 --name=lv_home --size=102400 --fstype=xfs
logvol /var --vg=vg0 --name=lv_var --size=500000 --grow --fstype=xfs
logvol /var/log --vg=vg0 --name=lv_var_log --size=20480 --fstype=xfs
logvol /var/log/audit --vg=vg0 --name=lv_var_log_audit --size=20480 --fstype=xfs
logvol /var/tmp --vg=vg0 --name=lv_var_tmp --size=20480 --fstype=xfs
logvol /tmp --vg=vg0 --name=lv_tmp --size=20480 --fstype=xfs
logvol swap --vg=vg0 --name=lv_swap --size=15360 --fstype=swap
%post --log=/root/ks-post.log
systemctl enable --now sshd
# Allow root login with password over SSH
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl restart sshd
for drv in qemu network nodedev nwfilter secret storage interface; do systemctl start virt${drv}d{,-ro,-admin}.socket; done
virt-host-validate
nmcli connection modify {{ mgmt_interface|default("eth0") }} ipv6.method ignore
%end
%post --log=/root/ks-post.log
systemctl enable --now sshd
# Allow root login with password over SSH
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl restart sshd
nmcli connection modify {{ mgmt_interface|default("eth0") }} ipv6.method ignore
%end
%packages
@^server-product-environment
@standard
aide
audit
expect
fapolicyd
firewalld
kexec-tools
opensc
openscap
openscap-scanner
openssh-server
python3
openssl-pkcs11
policycoreutils
postfix
rng-tools
rsyslog
rsyslog-gnutls
scap-security-guide
tmux
usbguard
-abrt
-abrt-addon-ccpp
-abrt-addon-kerneloops
-abrt-cli
-abrt-plugin-sosreport
-iprutils
-krb5-server
-krb5-workstation
-libreport-plugin-logger
-python3-abrt-addon
-rsh-server
-sendmail
-telnet-server
-tftp-server
-tuned
-vsftpd
%end
Server with GUI
Download: rhel-kickstart-server-with-gui.j2 Quick reference copy
%pre
wget http://{{ pxe_server }}/started || true
udevadm settle
dmsetup remove_all
# De-activate any exiting Volume Groups
vgchange -an system
vgchange -an os
# Clear software raid devices if any
raid_devices=$(mktemp /tmp/mdstat.XXXXXXXXX)
grep '^md' /proc/mdstat | cut -d: -f1 > "$raid_devices"
if [ -s "$raid_devices" ]; then
for raid in $(cat "$raid_devices"); do
wipefs -f -a "/dev/$raid"
mdadm --stop -f "/dev/$raid"
if [ $? != "0" ]; then
udevadm settle
dmsetup remove_all
mdadm --stop -f "/dev/$raid"
fi
done
else
echo "All RAID devices are cleared"
fi
rm -vf "$raid_devices"
# Wipe every detected block device that looks like an install target
install_disks=$(lsblk -ndo NAME,TYPE | awk '$2 == "disk" {print "/dev/" $1}')
if [ -z "$install_disks" ]; then
echo "No block devices detected to wipe"
else
for disk in $install_disks; do
echo "Wiping signatures on $disk"
wipefs -f -a "$disk"
done
fi
%end
# Accept Eula
eula --agreed
# Firewall configuration
firewall --enabled --ssh
# Keyboard layouts
keyboard 'us'
# Timezone
timezone Etc/UTC --isUtc
# Network information
network --bootproto=static --device={{ mgmt_interface|default("eth0") }} --gateway={{ gateway }} --ip={{ ip }} --netmask={{ netmask }} {% if dns_servers %}--nameserver={{ dns_servers | first }}{% endif %} --hostname={{ hostname }}
# Root PW
{% if pwd is defined and pwd %}
rootpw --iscrypted {{ pwd }}
{% elif pw is defined and pw %}
rootpw {{ pw }}
{% else %}
# rootpw not set
{% endif %}
# Reboot after installation
reboot
# Use text mode install
text
# System language
lang en_US
# SELinux configuration
selinux --permissive
{% if pwd is defined and pwd %}
user --name={{ username }} --groups=wheel --password={{ pwd }} --iscrypted
{% elif pw is defined and pw %}
user --name={{ username }} --groups=wheel --password={{ pw }}
{% else %}
# user password not set
{% endif %}
# Partition clearing information
clearpart --all --initlabel
# Disk partitioning information
## EFI partition (required for UEFI)
part /boot/efi --fstype="efi" --size=600 --fsoptions="umask=0077,shortname=winnt" --ondisk={{ install_device }}
## Boot partition
part /boot --fstype="xfs" --size=1024 --ondisk={{ install_device }}
##Create LVM physical volume
part pv.01 --size=16384 --grow --ondisk={{ install_device }}
## Create volume group
volgroup vg0 pv.01
## Logical volumes
logvol / --vg=vg0 --name=lv_root --size=20480 --fstype=xfs
logvol /home --vg=vg0 --name=lv_home --size=102400 --fstype=xfs
logvol /var --vg=vg0 --name=lv_var --size=500000 --grow --fstype=xfs
logvol /var/log --vg=vg0 --name=lv_var_log --size=20480 --fstype=xfs
logvol /var/log/audit --vg=vg0 --name=lv_var_log_audit --size=20480 --fstype=xfs
logvol /var/tmp --vg=vg0 --name=lv_var_tmp --size=20480 --fstype=xfs
logvol /tmp --vg=vg0 --name=lv_tmp --size=20480 --fstype=xfs
logvol swap --vg=vg0 --name=lv_swap --size=15360 --fstype=swap
%post --log=/root/ks-post.log
systemctl enable --now sshd
# Allow root login with password over SSH
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl restart sshd
nmcli connection modify {{ mgmt_interface|default("eth0") }} ipv6.method ignore
%end
%packages
@^server-with-gui-environment
@graphical-server-environment
@graphical-admin-tools
@gnome-desktop
aide
audit
expect
fapolicyd
firewalld
kexec-tools
opensc
openscap
openscap-scanner
openssh-server
python3
openssl-pkcs11
policycoreutils
postfix
rng-tools
rsyslog
rsyslog-gnutls
scap-security-guide
tmux
usbguard
-abrt
-abrt-addon-ccpp
-abrt-addon-kerneloops
-abrt-cli
-abrt-plugin-sosreport
-iprutils
-krb5-server
-krb5-workstation
-libreport-plugin-logger
-python3-abrt-addon
-rsh-server
-sendmail
-telnet-server
-tftp-server
-tuned
-vsftpd
%end
Virtualization Host
Download: rhel-kickstart-virtualization-host.j2 Quick reference copy
%pre
wget http://{{ pxe_server }}/started || true
udevadm settle
dmsetup remove_all
# De-activate any exiting Volume Groups
vgchange -an system
vgchange -an os
# Clear software raid devices if any
raid_devices=$(mktemp /tmp/mdstat.XXXXXXXXX)
grep '^md' /proc/mdstat | cut -d: -f1 > "$raid_devices"
if [ -s "$raid_devices" ]; then
for raid in $(cat "$raid_devices"); do
wipefs -f -a "/dev/$raid"
mdadm --stop -f "/dev/$raid"
if [ $? != "0" ]; then
udevadm settle
dmsetup remove_all
mdadm --stop -f "/dev/$raid"
fi
done
else
echo "All RAID devices are cleared"
fi
rm -vf "$raid_devices"
# Wipe every detected block device that looks like an install target
install_disks=$(lsblk -ndo NAME,TYPE | awk '$2 == "disk" {print "/dev/" $1}')
if [ -z "$install_disks" ]; then
echo "No block devices detected to wipe"
else
for disk in $install_disks; do
echo "Wiping signatures on $disk"
wipefs -f -a "$disk"
done
fi
%end
# Accept Eula
eula --agreed
# Firewall configuration
firewall --enabled --ssh
# Keyboard layouts
keyboard 'us'
# Timezone
timezone Etc/UTC --isUtc
# Network information
network --bootproto=static --device={{ mgmt_interface|default("eth0") }} --gateway={{ gateway }} --ip={{ ip }} --netmask={{ netmask }} {% if dns_servers %}--nameserver={{ dns_servers | first }}{% endif %} --hostname={{ hostname }}
# Root PW
{% if pwd is defined and pwd %}
rootpw --iscrypted {{ pwd }}
{% elif pw is defined and pw %}
rootpw {{ pw }}
{% else %}
# rootpw not set
{% endif %}
# Reboot after installation
reboot
# Use text mode install
text
# System language
lang en_US
# SELinux configuration
selinux --permissive
{% if pwd is defined and pwd %}
user --name={{ username }} --groups=wheel --password={{ pwd }} --iscrypted
{% elif pw is defined and pw %}
user --name={{ username }} --groups=wheel --password={{ pw }}
{% else %}
# user password not set
{% endif %}
# Partition clearing information
clearpart --all --initlabel
# Disk partitioning information
## EFI partition (required for UEFI)
part /boot/efi --fstype="efi" --size=600 --fsoptions="umask=0077,shortname=winnt" --ondisk={{ install_device }}
## Boot partition
part /boot --fstype="xfs" --size=1024 --ondisk={{ install_device }}
##Create LVM physical volume
part pv.01 --size=16384 --grow --ondisk={{ install_device }}
## Create volume group
volgroup vg0 pv.01
## Logical volumes
logvol / --vg=vg0 --name=lv_root --size=20480 --fstype=xfs
logvol /home --vg=vg0 --name=lv_home --size=102400 --fstype=xfs
logvol /var --vg=vg0 --name=lv_var --size=500000 --grow --fstype=xfs
logvol /var/log --vg=vg0 --name=lv_var_log --size=20480 --fstype=xfs
logvol /var/log/audit --vg=vg0 --name=lv_var_log_audit --size=20480 --fstype=xfs
logvol /var/tmp --vg=vg0 --name=lv_var_tmp --size=20480 --fstype=xfs
logvol /tmp --vg=vg0 --name=lv_tmp --size=20480 --fstype=xfs
logvol swap --vg=vg0 --name=lv_swap --size=15360 --fstype=swap
%post --log=/root/ks-post.log
systemctl enable --now cockpit.socket
systemctl enable --now sshd
# Allow root login with password over SSH
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl restart sshd
for drv in qemu network nodedev nwfilter secret storage interface; do systemctl start virt${drv}d{,-ro,-admin}.socket; done
virt-host-validate
nmcli connection modify {{ mgmt_interface|default("eth0") }} ipv6.method ignore
%end
%packages
@^virtualization-host-environment
@^graphical-server-environment
@graphical-admin-tools
@virtualization-client
@virtualization-hypervisor
@virtualization-tools
aide
audit
expect
fapolicyd
firewalld
kexec-tools
qemu-kvm
cockpit-machines
virt-v2v
opensc
openscap
openscap-scanner
openssh-server
python3
openssl-pkcs11
policycoreutils
postfix
rng-tools
rsyslog
rsyslog-gnutls
scap-security-guide
tmux
usbguard
virt-install
virt-viewer
-abrt
-abrt-addon-ccpp
-abrt-addon-kerneloops
-abrt-cli
-abrt-plugin-sosreport
-iprutils
-krb5-server
-krb5-workstation
-libreport-plugin-logger
-python3-abrt-addon
-rsh-server
-sendmail
-telnet-server
-tftp-server
-tuned
-vsftpd
%end
Additional Quick Templates
The sections below provide smaller starter snippets for other Mantle workflows.
VMware Datacenter Build: Host & Network JSON
Match the VMware Datacenter Build form fields and keep a clean, version-controlled baseline for each program.
{
"esxi": {
"hosts": [
{
"host_ip": "10.20.30.11",
"hostname": "esxi-a.mantle.lab",
"username": "root",
"password": "CHANGEME",
"subnet_mask": "255.255.255.0",
"default_gateway": "10.20.30.1",
"dns_server_0": "10.20.1.10",
"dns_server_1": "10.20.1.11",
"ntp_server_0": "pool.ntp.org",
"ntp_server_1": "time.navy.mil",
"vlan": 20,
"vmk0_mtu": 1500,
"enable_ssh": true
}
],
"vSwitch0": {
"standard_uplink": "vmnic0",
"additional_uplinks": ["vmnic1"],
"vm_network": "Mgmt",
"vlan": 20,
"mtu": 1500
}
}
}
Physical Network Device Template (Jinja2)
Use this baseline for serial-driven network builds referenced in Physical Network Builds.
hostname {{ device.hostname }}
!
interface mgmt0
description Mantle Mgmt
ip address {{ device.mgmt_ip }} {{ device.mgmt_mask }}
no shutdown
!
interface {{ device.build_interface }}
description Mantle Build VLAN {{ device.build_vlan }}
switchport trunk encapsulation dot1q
switchport trunk allowed vlan {{ device.allowed_vlans|join(',') }}
!
ip route 0.0.0.0/0 {{ device.default_gateway }}
username {{ device.username }} privilege 15 secret {{ device.password }}
ip domain-name {{ device.domain }}
ip name-server {{ device.dns_primary }} {{ device.dns_secondary }}
line vty 0 4
transport input ssh
login local
end
Cisco CSR1000v / C8000v Base Configuration
Leverage this IOS-XE template when staging Mantle-managed CSR1000v or C8000v routers. It aligns with the Physical Network Builds workflow, covering AAA, management plane hardening, core interfaces, and VTY controls. Download the full file at csr1000v-base-config.j2.
!
! Cisco CSR1000v / C8000v General Base Configuration Template
!
! Enable dna-advantage
license boot level network-advantage addon dna-advantage
!
hostname {{ hostname }}
!
service timestamps debug datetime msec
service timestamps log datetime msec
service password-encryption
no ip domain-lookup
ip domain name {{ domain_name }}
!
username {{ admin_username }} privilege 15 secret {{ admin_secret }}
enable secret {{ enable_secret }}
!
clock timezone UTC 0 0
!
! DNS / Name Resolution
!
ip name-server {{ dns_server_1 }}
!
! Local Banner
!
banner motd ^
{{ motd_banner }}
^
!
! SSH Configuration
!
crypto key generate rsa modulus {{ rsa_modulus }}
ip ssh version 2
ip ssh time-out {{ ssh_timeout }}
ip ssh authentication-retries {{ ssh_auth_retries }}
!
! AAA
!
aaa new-model
aaa authentication login default local
aaa authorization exec default local
!
! Logging
!
logging buffered 64000
logging console warnings
!
! NTP
!
ntp server {{ ntp_server_1 }}
!
! Loopback Interface
!
interface Loopback0
description {{ loopback_description }}
ip address {{ loopback_ip }} {{ loopback_mask }}
no shutdown
!
! Management Interface
!
interface GigabitEthernet1
description {{ mgmt_description }}
ip address {{ mgmt_ip }} {{ mgmt_mask }}
no shutdown
!
! WAN / Uplink Interface
!
interface GigabitEthernet2
description {{ wan_description }}
ip address {{ wan_ip }} {{ wan_mask }}
no shutdown
!
! LAN Interface
!
interface GigabitEthernet3
description {{ lan_description }}
ip address {{ lan_ip }} {{ lan_mask }}
no shutdown
!
! Default Route
!
ip route 0.0.0.0 0.0.0.0 {{ default_gateway }}
!
! VTY / Console Access
!
line console 0
logging synchronous
exec-timeout {{ console_exec_timeout_minutes }} {{ console_exec_timeout_seconds }}
login local
!
line vty 0 4
transport input ssh
exec-timeout {{ vty_exec_timeout_minutes }} {{ vty_exec_timeout_seconds }}
login local
!
line vty 5 15
transport input ssh
exec-timeout {{ vty_exec_timeout_minutes }} {{ vty_exec_timeout_seconds }}
login local
!
! Save Configuration
!
end
wr mem
Mobile Provision Baseline Stub
Reference this JSON for Mobile Device Provisions baselines.
{
"baseline_name": "Mantle-Mobile-Standard",
"apks": ["ATAK-4.9.0.apk", "MantleMobile.apk"],
"configs": ["atak_config.json", "vpn_profile.json"],
"prep": {
"wifi_ssid": "EDGE-LAB",
"wifi_password": "SUPPORT123",
"screen_timeout_minutes": 10
},
"devices": [
{
"serial": "RFCR91XYZ",
"pin": "1234",
"unique_files": ["/configs/unit1.json"]
}
]
}
Mantle Flow DAG Skeleton
Use this Airflow DAG to validate Mantle Flow licensing and execution described in Mantle Flow.
"""
BASE ANSIBLE DAG TEMPLATE (with XCom + remediation sample)
=========================================================
**Purpose** Create Airflow DAGs that trigger Ansible playbooks through the FastAPI
backend, automatically pull structured output (`json_stdout`) into XCom, and
(optionally) remediate failed hosts.
**Core pieces**
1️⃣ **Trigger tasks** (`HttpOperator`) → POST payload to backend.
2️⃣ **Sensor tasks** (`HttpSensor`) → poll status, push `json_stdout` to XCom.
**Key guarantees**
* Do **not** change *Section 1* constants — backend depends on them.
* Keep the helper factories intact; extend logic only in *Section 3*.
Down‑stream tasks read the playbook result with:
#python
#ti.xcom_pull(task_ids="wait_<task_id>", key="json_stdout")
#```
"""
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 1 • CONFIGURATION (DO NOT EDIT UNLESS YOU KNOW WHAT YOU’RE DOING)
# ─────────────────────────────────────────────────────────────────────────────
import json
from datetime import datetime, timezone
from typing import Any, Dict, List, Union, Any
from airflow import DAG
from airflow.exceptions import AirflowFailException
from airflow.providers.http.operators.http import HttpOperator
from airflow.providers.http.sensors.http import HttpSensor
from airflow.operators.python import PythonOperator, BranchPythonOperator
from airflow.operators.empty import EmptyOperator
from airflow.utils.trigger_rule import TriggerRule
from airflow.utils.context import Context
BACKEND_CONN_ID = "backend_http" # Airflow connection id
TRIGGER_ENDPOINT = "api/ansible/airflow/playbook/run" # Backend POST
HEADERS = {
"Content-Type": "application/json",
"Authorization": "Bearer {{ dag_run.conf.get('auth_token') }}",
}
DEFAULT_EXTRA_VARS: Dict[str, Any] = {}
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 2 • HELPERS (DO NOT EDIT UNLESS YOU KNOW WHAT YOU’RE DOING)
# ─────────────────────────────────────────────────────────────────────────────
def create_trigger_task(
playbook: str,
task_id: str,
*,
extra_vars: Union[Any, None] = None,
) -> HttpOperator:
"""Return an `HttpOperator` that triggers *playbook* on the backend."""
payload = {
"dag_id": "{{ dag.dag_id }}",
"dag_run_id": "{{ dag_run.conf.get('dag_run_id') }}",
"task_id": task_id,
"playbook": playbook,
"extra_vars": extra_vars if extra_vars is not None else DEFAULT_EXTRA_VARS,
}
return HttpOperator(
task_id=task_id,
http_conn_id=BACKEND_CONN_ID,
endpoint=TRIGGER_ENDPOINT,
method="POST",
data=json.dumps(payload),
headers=HEADERS,
log_response=True,
)
class HttpSensorWithXCom(HttpSensor):
"""Sensor that pushes `json_stdout` to XCom and fails fast on status==failed."""
template_fields = HttpSensor.template_fields
def poke(self, context: Context) -> bool: # type: ignore[override]
resp = self.hook.run(
method=self.method, endpoint=self.endpoint, headers=self.headers
)
try:
data: Dict[str, Any] = resp.json()
except Exception as exc:
raise AirflowFailException(
f"{self.task_id}: response not JSON → {resp.text}"
) from exc
status = str(data.get("status", "")).lower()
if status == "failed":
raise AirflowFailException(f"{self.task_id}: backend reported FAILURE")
if (stdout := data.get("json_stdout")) is not None:
context["ti"].xcom_push(key="json_stdout", value=stdout)
return status == "success"
def create_sensor_task(task_id: str) -> HttpSensorWithXCom:
"""Factory for customised sensor (polls `/status/<dag_run_id>/<task_id>`)."""
endpoint = (
"api/ansible/airflow/playbook/status/"
"{{ dag_run.conf.get('dag_run_id') }}/"
f"{task_id}"
)
return HttpSensorWithXCom(
task_id=f"wait_{task_id}",
http_conn_id=BACKEND_CONN_ID,
endpoint=endpoint,
method="GET",
headers=HEADERS,
poke_interval=15,
timeout=60 * 60,
mode="reschedule",
)
# Helper: build extra‑vars for remediation playbook
def _build_remediation_vars(failed_hosts: List[str]) -> Dict[str, Any]:
return {"target_hosts": failed_hosts}
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 3 • DAG DEFINITION (FREE TO EXTEND BELOW)
# ─────────────────────────────────────────────────────────────────────────────
"""
Workflow overview
-----------------
• step‑1 playbook runs → sensor grabs host statuses.
• Branch: if any host != "ok" → run remediation playbook.
• Paths merge, then run step‑2 and step‑3 normally.
"""
with DAG(
dag_id="dag_template",
start_date=datetime.now(timezone.utc),
schedule_interval=None,
catchup=False,
default_args={"owner": "airflow", "retries": 0, "depends_on_past": False},
tags=["example", "ansible"],
) as dag:
# ► Step 1
trigger_1 = create_trigger_task("step01_dummy.yml", "run_step1")
sensor_1 = create_sensor_task("run_step1")
# ► Branch on host failures
def _branch(**context) -> str:
data: Dict[str, str] = (
context["ti"].xcom_pull(task_ids="wait_run_step1", key="json_stdout") or {}
)
failed = [host for host, status in data.items() if status.lower() != "ok"]
context["ti"].xcom_push(key="failed_hosts", value=failed)
return "remediate_hosts" if failed else "skip_remediation"
branch = BranchPythonOperator(task_id="branch_on_failures", python_callable=_branch)
# ► Remediation path
def _make_extra_vars(**context):
failed: List[str] = (
context["ti"].xcom_pull(key="failed_hosts", task_ids="branch_on_failures")
or []
)
return _build_remediation_vars(failed)
build_vars = PythonOperator(
task_id="build_remediation_vars", python_callable=_make_extra_vars
)
remediation_trigger = create_trigger_task(
"remediate_failed_hosts.yml",
"remediate_hosts",
extra_vars="{{ task_instance.xcom_pull(task_ids='build_remediation_vars') }}",
)
remediation_sensor = create_sensor_task("remediate_hosts")
skip_remediation = EmptyOperator(task_id="skip_remediation")
join = EmptyOperator(
task_id="rejoin_paths", trigger_rule=TriggerRule.NONE_FAILED_MIN_ONE_SUCCESS
)
# ► Continue pipeline
trigger_2 = create_trigger_task("step02_dummy.yml", "run_step2")
sensor_2 = create_sensor_task("run_step2")
trigger_3 = create_trigger_task("step03_dummy.yml", "run_step3")
sensor_3 = create_sensor_task("run_step3")
# Dependencies
trigger_1 >> sensor_1 >> branch
branch >> build_vars >> remediation_trigger >> remediation_sensor >> join
branch >> skip_remediation >> join
join >> trigger_2 >> sensor_2 >> trigger_3 >> sensor_3
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
dag.cli() # `python <file>.py test` for local debug
Tip: Store the templates above in source control (Git, CodeCommit, etc.) so you always know which version was uploaded to Mantle and can track changes across programs.