├── LICENSE ├── README.rst └── lf-rhel.cfg /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Generic Kickstart File 2 | ====================== 3 | 4 | This is a generic kickstart file for a minimal install of RHEL 7+ and compatible, on both BIOS as well as UEFI machines. 5 | 6 | 7 | What are Kickstart Installations? 8 | --------------------------------- 9 | 10 | Kickstart files contain answers to all questions normally asked by the installation program, such as what time zone you want the system to use, how the drives should be partitioned, or which packages should be installed. Providing a prepared kickstart file when the installation begins therefore allows you to perform the installation automatically, without need for any intervention from the user. This is especially useful when deploying Red Hat Enterprise Linux (RHEL) on a large number of systems at once. 11 | 12 | Kickstart files can be kept on a single server system and read by individual computers during the installation. This installation method can support the use of a single kickstart file to install RHEL and compatible on multiple machines, making it ideal for network and system administrators. (`Source `_) 13 | 14 | 15 | How to use 16 | ---------- 17 | 18 | This kickstart file can be used by booting from an ISO file, then either 19 | 20 | * BIOS: pressing ``ESC`` on the first screen and providing these cmdline arguments (line breaks are only for better readability): 21 | 22 | .. code-block:: text 23 | 24 | boot: linux inst.ks=https://raw.githubusercontent.com/Linuxfabrik/kickstart/main/lf-rhel.cfg 25 | [lftype=cis|cloud|cloud-cis|minimal] 26 | [lfdisk=$DISK] 27 | [ip=[IPADDRESS]::GATEWAY:NETMASK::INTERFACE:none] 28 | [nameserver=NAMESERVER] 29 | [...] 30 | 31 | * UEFI: pressing ``e`` on the "Install ..." entry and appending the following to the ``linuxefi`` cmdline (line breaks are only for better readability), then booting by pressing ``Ctrl-X``. 32 | 33 | .. code-block:: text 34 | 35 | linuxefi ... inst.ks=https://raw.githubusercontent.com/Linuxfabrik/kickstart/main/lf-rhel.cfg 36 | [lftype=cis|cloud|cloud-cis|minimal] 37 | [lfdisk=$DISK] 38 | [ip=[IPADDRESS]::GATEWAY:NETMASK::INTERFACE:none] 39 | [nameserver=NAMESERVER] 40 | [...] 41 | 42 | Note that ``ip=`` is an array (for providing multiple IP addresses), so the inner brackets are mandatory. 43 | 44 | For convenience, we provide http://linuxfabrik.ch/ks as a shortened URL to https://raw.githubusercontent.com/Linuxfabrik/kickstart/main/lf-rhel.cfg. 45 | 46 | 47 | What this Kickstart File does 48 | ----------------------------- 49 | 50 | * Supports RHEL 7+ and compatible. 51 | * Works on legacy BIOS as well as UEFI. 52 | * The kickstart file is intended to provide a minimal installation, with ``firewalld`` disabled and SELinux in "Enforcing" mode. 53 | * Can be installed on a user-defined disk by specifying the kernel cmdline argument ``lfdisk=$DISK``. If unset, it tries to find the first block device, in the order ``vda`` > ``sda`` > ``nvme0n1``, and fails otherwise. 54 | * There are two users: ``linuxfabrik`` and ``root``. The root account is always locked by default. This means that the root user will not be able to log in from any console. ``root`` has no password and no SSH keys. Login with the ``linuxfabrik`` user, which is also part of the the ``wheel`` group. ``sudo`` is configured to gain root. 55 | 56 | The kickstart file can be used to install different types of minimal installs by setting the kernel cmdline argument ``lftype=``: 57 | 58 | .. csv-table:: 59 | :header-rows: 1 60 | 61 | ``lftype=``, Install Type, Partitioning Scheme, Password of User ``linuxfabrik``, SSH Keys of User ``linuxfabrik`` 62 | ``cis``, Minimal, "CIS, LVM", ``password``, Those of Linuxfabrik 63 | ``cloud``, Minimal, "One partition, LVM", unset (inject via ``cloud-init``), none (inject via ``cloud-init``) 64 | ``cloud-cis``, Minimal, "CIS, LVM", unset (inject via ``cloud-init``), none (inject via ``cloud-init``) 65 | ``minimal`` (default), Minimal, "One partition, LVM", ``password``, Those of Linuxfabrik 66 | 67 | 68 | Useful Kernel Cmdline Arguments 69 | ------------------------------- 70 | 71 | RHEL: 72 | 73 | * ``inst.loglevel=[debug|info]``: Note: Option is removed in RHEL9 and is always set to debug 74 | * ``inst.ks=[hd::|[http,https,ftp]:///|nfs:[:]:/`` (MANDATORY) 75 | * ``inst.noverifyssl``: Prevents Anaconda from verifying the ssl certificate for all HTTPS connections ("insecure") 76 | * ``inst.nosave=[,]`` (options: ``input_ks,output_ks,all_ks,logs,all``) 77 | * ``inst.rescue`` 78 | * `more Kernel Cmdline Arguments `_ 79 | 80 | Specific to this kickstart file: 81 | 82 | * ``lfdisk=$DISK``: User-defined disk for installing the OS. Default: unset (so tries to find the first block device, in the order ``vda`` > ``sda`` > ``nvme0n1``, and fails otherwise). 83 | * ``lftype``: See table above. Defaults to ``minimal``. 84 | 85 | 86 | Modifying this Kickstart 87 | ------------------------ 88 | 89 | This kickstart includes an additional kickstart ``/tmp/dynamic.ks``. This ``/tmp/dynamic.ks`` file is generated in a kickstart pre-script. 90 | At the beginning of the pre-script the available Python version is determined, the Python script is written to ``/tmp/pre-script.py`` and executed. 91 | The Python script then generates the ``/tmp/dynamic.ks``. 92 | This Python script can be modified as follows: 93 | 94 | * | ``lfkeys`` 95 | | An array of SSH keys that will be installed for either the ``root`` or ``linuxfabrik`` user depending on ``lftype`` as documented above. 96 | * | ``packages_`` 97 | | An array with package names for a ```` install. 98 | * | ``part_`` 99 | | An array of kickstart ``logvol`` lines that define logical volumes for ```` as documented in Fedora Kickstart Syntax Reference: `logvol `_ 100 | * | ``users_`` 101 | | A string containing a ":" separated list in the form ``name="":cmd="":keys=""`` (Fedora Kickstart Syntax Reference: `user `_, `rootpw `_) 102 | * | ``post_`` 103 | | A multiline string containing the postscript for ````. Will be executed by ``/bin/sh``. 104 | 105 | 106 | Known Limitations 107 | ----------------- 108 | 109 | This kickstart file does not work for RHEL 6- (and compatible). 110 | 111 | 112 | Tests 113 | ----- 114 | 115 | Test combinations: 116 | 117 | * OS: centos7, rocky8, rocky9 118 | * Firmware: BIOS, UEFI 119 | * Disk: vda, sda 120 | * ``lftype``: ``cis``, ``cloud``, ``cloud-cis``, ``minimal`` 121 | 122 | What to test within the VM: 123 | 124 | * Console login using "root" + "password": Should not work. 125 | * Console login using "linuxfabrik" + "password": Should work on non-cloud. On cloud, password depends on cloud-init. 126 | * ``ip a``: Should get an IP. 127 | * ``ssh root@vm``: Should not work. 128 | * ``ssh linuxfabrik@vm``: Should work on non-cloud. On cloud, it depends on cloud-init. 129 | * ``sudo su -``: Should work. 130 | * ``cat /etc/shadow``: Should show that root's password is locked. 131 | * ``df -hT``: One partition on non-cis, 7 partitions on cis. 132 | * ``lvs``: Should work. 133 | * ``sudo dnf -y install nano``: Should work. 134 | * ``systemctl status cloud-init``: Not found on non-cloud, should work on cloud. 135 | * ``systemctl status firewalld``: Should work. 136 | * ``ll /root``: Should list at least two Anaconda files. 137 | 138 | 139 | Troubleshooting 140 | --------------- 141 | 142 | * ``page_poison=1`` kernel cmdline option installed by bootloader cmd can leave the system unbootable due to a buggy UEFI firmware. This was observed with TianoCore firmware on qemu. Remove this option to boot. See https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/8.7_release_notes/known-issues. 143 | * Fedora 38: We observed problems booting into the installer. Try ``inst.neednet=1 rd.debug`` to get to the installer. 144 | * Last resort: If this Kickstart file doesn't work, copy it to a webserver and modify it to suit your needs. 145 | 146 | 147 | References 148 | ---------- 149 | 150 | * `Fedora Kickstart Syntax `_ 151 | * `RHEL 7 Kickstart Syntax `_ 152 | * `RHEL 8 Kickstart Syntax `_ 153 | * `RHEL 9 Kickstart Syntax `_ 154 | * `Rocky 8 Generic Cloud LVM Kickstart `_ 155 | * `OpenStack Image Requirements `_ 156 | -------------------------------------------------------------------------------- /lf-rhel.cfg: -------------------------------------------------------------------------------- 1 | # MAIN KICKSTART 2 | 3 | # Author: Linuxfabrik GmbH, Zurich, Switzerland 4 | # Contact: info (at) linuxfabrik (dot) ch 5 | # https://www.linuxfabrik.ch/ 6 | # License: The Unlicense, see LICENSE file. 7 | 8 | # This kickstart file consists of two parts. 9 | # * The main part, which is quite minimal, 10 | # * and a dynamic part (/tmp/dynamic.ks) that gets written during the %pre script. 11 | # The %pre script starts by parsing some options from the kernel cmdline and 12 | # detecting the RHEL version. 13 | # It then fills /tmp/dynamic.ks depending on these variables. 14 | 15 | # The order of the commands is relatively compatible with the output of 16 | # https://access.redhat.com/labs/kickstartconfig/ 17 | 18 | # See the README for more details. 19 | 20 | 21 | # System language 22 | lang en_US.UTF-8 23 | 24 | # Keyboard layouts 25 | keyboard us 26 | 27 | # System timezone 28 | timezone Europe/Zurich --utc 29 | 30 | # Shutdown after installation 31 | shutdown 32 | 33 | # Use text mode install and CDROM installation media 34 | text 35 | cdrom 36 | 37 | # This command is required when performing an unattended installation on a system with previously 38 | # initialized disks. 39 | zerombr 40 | 41 | # Automatically creates partitions required by your hardware platform, eg /boot/efi or biosboot 42 | reqpart 43 | 44 | # Network information 45 | network --hostname=localhost.localdomain 46 | # note: do not set any other network options. dhcp is the default anyway, and setting them here disregards the ip and nameserver settings on the kernel command line 47 | # network --bootproto=dhcp --device=link --activate --onboot=on 48 | 49 | # Do not configure the X Window System 50 | skipx 51 | 52 | # Disable the Setup Agent on first boot 53 | firstboot --disable 54 | 55 | # State of SELinux on the installed system 56 | selinux --enforcing 57 | 58 | # Firewalling should be done later on by the admin. Required for the cloud 59 | firewall --disable 60 | 61 | # System services 62 | services --disabled="kdump" --enabled="NetworkManager,sshd" 63 | 64 | # Disable kdump by default, frees up some memory 65 | %addon com_redhat_kdump --disable 66 | %end 67 | 68 | %include /tmp/dynamic.ks 69 | 70 | 71 | 72 | %pre --logfile /tmp/kickstart.install.pre.log --erroronfail 73 | # fedora doesn't have platform-python and rhel8 only has platform-python, so we'll have to 74 | # detect what we have 75 | PYTHON=$(ls /usr/libexec/platform-python /usr/bin/python3 2> /dev/null | head -n1) 76 | cat << EOT > /tmp/pre-script.py 77 | #!$PYTHON 78 | import os 79 | import re 80 | import stat 81 | import sys 82 | 83 | 84 | def isblockdevice(path): 85 | return os.path.exists(path) and stat.S_ISBLK(os.stat(path).st_mode) 86 | 87 | 88 | lfkeys = [ 89 | 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDuYw1auj8Lo6l5Ie8H7q419pKNjD3LSDZpFLNI0KehO chris.drescher@linuxfabrik.ch', 90 | 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDaWaDOEGqhZy1XD3a0IJKPvuB0wz2Yzc7Y8b2E91PPDSfi2wczUjZ9T2f8J7fw46i6O8dUFdsle8HePlVt1f6P+SPr84KIvte4sCXPGjHO7UlP+0biPl1FJbe+LU6akGVgAhc37CTn7h10COim5TpIdmPfn8A1y0Y8G3GNTVELSC4GG6rJLgme++OTkNlenH+L7KSobQE1v+MS4mRjrg+qitgPzBv1VgTWJff4d3vdEtb9zwVdzZzyXcdP5/nWH54iDaZawLsyvPXG2VDQq9bUn4CbZ+/ldrVg+yi8Y5RlSLFlbaX2XKnqf8mC3fpszXrmZ93d/idvCLDQ2ijV1FQzYLNg9nstV6VHCek1+g0u3oQ17CyRRCuxSRf3kDagO/+FMGwIliQTSX8rTx0epYzj4vUKA0nYIHXhwklUTG2PFNgJ1Nfxllqblij/PbFfoJCCp+st7/ewYYiclV6jrAg01/bqAvrdS8PW6JQpTIeuJRZhkrvCxgzDsuYE+EqTHxyBs7Uxu9D8QvgZEYiwuClRE92xPNmuEk/BDpX9s8mcXtamp57AUESRFWPFUZoDFuHRjHz56lXJ0UIfDR7XDZHumdQRt6jh7Sj0YGVN3Es9UIqka9dZRiXLdZ9q/C0IReZKtcKDSIn/xB1Kt2qAIRLpSG3IX8JmONOa1h/Gns1NKw== markus.frei@linuxfabrik.ch', 91 | 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM5DxWzuUlSfdHHE1c4oQ2cbC0TyjXkVCuNKBJvn7TvX navid.sassan@linuxfabrik.ch', 92 | ] 93 | lftype='minimal' 94 | lfdisk = None 95 | for guess in ['vda', 'sda', 'nvme0n1']: 96 | if isblockdevice('/dev/{}'.format(guess)): 97 | lfdisk = guess 98 | break 99 | 100 | print('Linuxfabrik Kickstart: Set kernel cmdline arguments') 101 | with open('/proc/cmdline', 'r') as file: 102 | cmdline = file.read() 103 | for item in cmdline.split(" "): 104 | if item.split("=")[0] == "lftype": 105 | lftype=item.split("=")[1].strip() 106 | elif item.split("=")[0] == "lfdisk": 107 | lfdisk=item.split("=")[1].strip() 108 | 109 | if not lfdisk: 110 | print('Linuxfabrik Kickstart: Lfdisk is not given and no disk to install to could be guessed. Aborting...') 111 | sys.exit(1) 112 | 113 | print('Linuxfabrik Kickstart: Lftype: {}, lfdisk: {}'.format(lftype, lfdisk)) 114 | 115 | print('Linuxfabrik Kickstart: Set bootloader') 116 | # * console=ttyS0,115200n8: 117 | # required for cloud images. 118 | # see https://docs.openstack.org/image-guide/openstack-images.html#ensure-image-writes-boot-log-to-console 119 | # * no_timer_check: 120 | # see https://opendev.org/openstack/diskimage-builder/commit/1ec93f43a8736171cb78382fd6184f03c001771b 121 | # * net.ifnames=0: 122 | # for cloud images, 'eth0' _is_ the predictable device name 123 | # * audit=1 audit_backlog_limit=8192: 124 | # required by CIS 125 | # * vsyscall=none: 126 | # due to https://www.stigviewer.com/stig/red_hat_enterprise_linux_8/2020-11-25/finding/V-230278 127 | bootloader_cis = 'bootloader --location=mbr --boot-drive={} --append="audit=1 audit_backlog_limit=8192 vsyscall=none"'.format(lfdisk) 128 | bootloader_cloud = 'bootloader --location=mbr --boot-drive={} --append="console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0 vsyscall=none"'.format(lfdisk) 129 | bootloader_cloud_cis = 'bootloader --location=mbr --boot-drive={} --append="console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0 audit=1 audit_backlog_limit=8192 vsyscall=none"'.format(lfdisk) 130 | bootloader_minimal = 'bootloader --location=mbr --boot-drive={} --append="vsyscall=none"'.format(lfdisk) 131 | 132 | print('Linuxfabrik Kickstart: Set packages') 133 | packages = [ 134 | '@core', 135 | '-plymouth', # No need for plymouth. Also means anaconda won't put rhgb/quiet on kernel command line 136 | ] 137 | packages_cis = [] 138 | packages_cloud = [ 139 | 'cloud-init', 140 | 'cloud-utils-growpart', 141 | 'dhcp-client', 142 | 'qemu-guest-agent', 143 | 'rng-tools', 144 | 'tuned', 145 | '-aic94xx-firmware', 146 | '-alsa-firmware', 147 | '-alsa-lib', 148 | '-alsa-tools-firmware', 149 | '-biosdevname', 150 | '-iprutils', 151 | '-ivtv-firmware', 152 | '-iwl100-firmware', 153 | '-iwl1000-firmware', 154 | '-iwl105-firmware', 155 | '-iwl135-firmware', 156 | '-iwl2000-firmware', 157 | '-iwl2030-firmware', 158 | '-iwl3160-firmware', 159 | '-iwl3945-firmware', 160 | '-iwl4965-firmware', 161 | '-iwl5000-firmware', 162 | '-iwl5150-firmware', 163 | '-iwl6000-firmware', 164 | '-iwl6000g2a-firmware', 165 | '-iwl6000g2b-firmware', 166 | '-iwl6050-firmware', 167 | '-iwl7260-firmware', 168 | '-langpacks-*', 169 | '-langpacks-en', 170 | '-libertas-sd8686-firmware', 171 | '-libertas-sd8787-firmware', 172 | '-libertas-usb8388-firmware', 173 | ] 174 | packages_cloud_cis = packages_cloud 175 | packages_minimal = [] 176 | 177 | print('Linuxfabrik Kickstart: Set partitioning schema') 178 | part_cis = [ 179 | 'logvol / --fstype="xfs" --size=4096 --vgname=rl --name=root', 180 | 'logvol /home --fstype="xfs" --size=1024 --vgname=rl --name=home --fsoptions="nodev,nosuid"', 181 | 'logvol /tmp --fstype="xfs" --size=1024 --vgname=rl --name=tmp --fsoptions="nodev,noexec,nosuid"', 182 | 'logvol /var --fstype="xfs" --size=4096 --vgname=rl --name=var --fsoptions="nodev,nosuid"', 183 | 'logvol /var/log --fstype="xfs" --size=2048 --vgname=rl --name=var_log --fsoptions="nodev,noexec,nosuid"', 184 | 'logvol /var/log/audit --fstype="xfs" --size=512 --vgname=rl --name=var_log_audit --fsoptions="nodev,noexec,nosuid"', 185 | 'logvol /var/tmp --fstype="xfs" --size=1024 --vgname=rl --name=var_tmp --fsoptions="nodev,noexec,nosuid"', 186 | 'logvol swap --fstype="swap" --recommended --vgname=rl --name=swap', 187 | ] 188 | part_cloud = [ 189 | 'logvol / --fstype="xfs" --size=1024 --vgname=rl --name=root --grow', 190 | 'logvol swap --fstype="swap" --recommended --vgname=rl --name=swap', 191 | ] 192 | part_cloud_cis = part_cis 193 | part_minimal = part_cloud 194 | 195 | print('Linuxfabrik Kickstart: Define users') 196 | users_cis = [ 197 | { 198 | 'name': 'linuxfabrik', 199 | 'cmd': 'user --name=linuxfabrik --groups=wheel --password=password --plaintext', 200 | 'keys': lfkeys, 201 | }, 202 | { 203 | 'name': 'root', 204 | 'cmd': 'rootpw --plaintext --lock password', 205 | 'keys': [], 206 | }, 207 | ] 208 | users_cloud = [ 209 | { 210 | 'name': 'linuxfabrik', 211 | 'cmd': 'user --name=linuxfabrik --groups=wheel --lock', 212 | 'keys': [], 213 | }, 214 | { 215 | 'name': 'root', 216 | 'cmd': 'rootpw --plaintext --lock password', 217 | 'keys': [], 218 | }, 219 | ] 220 | users_cloud_cis = users_cloud 221 | users_minimal = users_cis 222 | 223 | print('Linuxfabrik Kickstart: Create post scripts') 224 | post_cis = ''' 225 | # allow password-less sudo 226 | cat > /etc/sudoers.d/linuxfabrik << EOF 227 | %linuxfabrik ALL=(ALL) NOPASSWD: ALL 228 | EOF 229 | ''' 230 | 231 | post_cloud = ''' 232 | # setup systemd to boot to the right runlevel 233 | echo "Setting default runlevel to multiuser text mode" 234 | rm -f /etc/systemd/system/default.target 235 | ln -s /lib/systemd/system/multi-user.target /etc/systemd/system/default.target 236 | 237 | # this is installed by default but we don't need it in virt 238 | # Commenting out the following for =1234504 239 | # rpm works just fine for removing this, no idea why yum can't cope 240 | echo "Removing linux-firmware package" 241 | rpm --erase linux-firmware 242 | 243 | # Remove firewalld; was supposed to be optional in F18+, but is pulled in 244 | # in install/image building. 245 | echo "Removing firewalld" 246 | # FIXME! clean_requirements_on_remove is the default with yum, but may 247 | # not work when package was installed by Anaconda instead of command line. 248 | # Also -- check if this is still even needed with new anaconda -- disabled 249 | # firewall should _not_ pull in this package. 250 | # yum -C -y remove "firewalld*" --setopt="clean_requirements_on_remove=1" 251 | yum --cacheonly -y erase "firewalld*" 252 | 253 | # Another one needed at install time but not after that, and it pulls 254 | # in some unneeded deps (like, newt and slang) 255 | echo "Removing authconfig" 256 | yum --cacheonly -y erase authconfig 257 | 258 | echo "Removing avahi" 259 | yum --cacheonly -y remove avahi\\* 260 | 261 | echo "Installing cloud-init cloud-utils-growpart rng-tools tuned" 262 | # these tools always fail to install in the %packages section, so try this here 263 | yum -y install cloud-init cloud-utils-growpart rng-tools tuned 264 | 265 | # Since the scriptlets from yum install are running in chroot, they can't do a daemon-reload 266 | # ("Running in chroot, ignoring command 'daemon-reload'"), so a "systemctl enable --now" would 267 | # fail. "systemctl enable" is enough here. 268 | systemctl enable cloud-init 269 | systemctl enable cloud-init-local 270 | systemctl enable cloud-config 271 | systemctl enable cloud-final 272 | systemctl enable rngd 273 | 274 | echo "Getty fixes" 275 | # although we want console output going to the serial console, we don't 276 | # actually have the opportunity to login there. FIX. 277 | # we don't really need to auto-spawn _any_ gettys. 278 | sed --in-place 's/^#NAutoVTs=.*/NAutoVTs=0/' /etc/systemd/logind.conf 279 | 280 | echo "Network fixes" 281 | # initscripts don't like this file to be missing. 282 | # and https://bugzilla.redhat.com/show_bug.cgi?id=1204612 283 | cat > /etc/sysconfig/network << EOF 284 | NETWORKING=yes 285 | NOZEROCONF=yes 286 | DEVTIMEOUT=10 287 | EOF 288 | 289 | echo "Truncate /etc/resolv.conf" 290 | # Anaconda is writing an /etc/resolv.conf from the install environment. 291 | # The system should start out with an empty file, otherwise cloud-init 292 | # will try to use this information and may error: 293 | # https://bugs.launchpad.net/cloud-init/+bug/1670052 294 | truncate --size=0 /etc/resolv.conf 295 | 296 | echo "Disable predictable network interface names" 297 | # For cloud images, 'eth0' _is_ the predictable device name, since 298 | # we don't want to be tied to specific virtual (!) hardware 299 | rm -f /etc/udev/rules.d/70* 300 | ln -s /dev/null /etc/udev/rules.d/80-net-name-slot.rules 301 | 302 | echo 'Set generic localhost names (/etc/hosts)' 303 | cat > /etc/hosts << EOF 304 | 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 305 | ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 306 | 307 | EOF 308 | 309 | # Because memory is scarce resource in most cloud/virt environments, 310 | # and because this impedes forensics, we are differing from the Fedora 311 | # default of having /tmp on tmpfs. 312 | # However, there is no need to disable the mount, as we are already overwriting the options 313 | # for /tmp above. 314 | # Masking the mount now would lead to a missing /tmp. 315 | # echo "Disabling tmpfs for /tmp." 316 | # systemctl mask tmp.mount 317 | 318 | echo 'Set tuned profile to virtual-guest' 319 | echo 'virtual-guest' > /etc/tuned/active_profile 320 | 321 | echo 'Make sure firstboot does not start' 322 | echo 'RUN_FIRSTBOOT=NO' > /etc/sysconfig/firstboot 323 | 324 | echo 'Adjust sudoers for the linuxfabrik user' 325 | cat > /etc/sudoers.d/linuxfabrik << EOF 326 | %linuxfabrik ALL=(ALL) NOPASSWD: ALL 327 | EOF 328 | sed --in-place 's/name: cloud-user/name: linuxfabrik/g' /etc/cloud/cloud.cfg 329 | 330 | echo 'Cleaning up old yum repodata' 331 | yum clean all 332 | 333 | echo 'Increase DHCP client retry/timeouts' 334 | # change dhcp client retry/timeouts to resolve =6866 335 | cat >> /etc/dhcp/dhclient.conf << EOF 336 | 337 | timeout 300; 338 | retry 60; 339 | EOF 340 | 341 | echo 'Adjust console output in /etc/default/grub and regenerate grub config' 342 | # If init script messages need to be seen on the serial console as well, it should be made 343 | # the primary by swapping the order of the console parameters: 344 | sed --in-place 's/console=ttyS0,115200n8 console=tty0/console=tty0 console=ttyS0,115200n8/' /etc/default/grub 345 | if [ -d /sys/firmware/efi/ ]; then 346 | grub_config_file=$(find /boot/efi -name grub.cfg) 347 | else 348 | grub_config_file=$(find /boot/ -name grub.cfg) 349 | fi 350 | if [ -z "$grub_config_file" ]; then 351 | echo 'Could not find a grub.cfg in /boot. Skipping grub2-mkconfig' 352 | else 353 | grub2-mkconfig --output=$grub_config_file 354 | fi 355 | 356 | echo "Removing random-seed so it's not the same in every image" 357 | rm -f /var/lib/systemd/random-seed 358 | 359 | echo 'Remove /etc/machine-id' 360 | # https://git.resf.org/sig_core/kickstarts/src/branch/r8/Rocky-8-GenericCloud-LVM.ks 361 | cat /dev/null > /etc/machine-id 362 | 363 | echo 'Remove various other log / cache files' 364 | rm -f /root/.wget-hsts 365 | rm -rf /root/anaconda-ks.cfg 366 | rm -rf /root/install.log 367 | rm -rf /root/install.log.syslog 368 | rm -rf /var/lib/yum/* 369 | rm -rf /var/log/anaconda* 370 | rm -rf /var/log/yum.log 371 | rm -rf /var/tmp/* 372 | find /var/log -type f -exec truncate --size=0 {} \\; 373 | export HISTSIZE=0 374 | cat /dev/null > ~/.bash_history 375 | history -c 376 | 377 | echo 'Fixing SELinux contexts' 378 | touch /var/log/cron 379 | touch /var/log/boot.log 380 | mkdir -p /var/cache/yum 381 | # ignore return code because UEFI systems with vfat filesystems 382 | # that don't support selinux will give us errors like 383 | # "Could not set context for /boot/efi/EFI/BOOT/BOOTX64.EFI: Operation not supported" 384 | /usr/sbin/fixfiles -R -a restore || true 385 | ''' 386 | 387 | post_cloud_cis = post_cloud 388 | post_minimal = post_cis 389 | 390 | 391 | print('Linuxfabrik Kickstart: Act on cmdargs & discovery for {}'.format(lftype)) 392 | if lftype == 'cloud': 393 | bootloader = bootloader_cloud 394 | packages += packages_cloud 395 | part = part_cloud 396 | post = post_cloud 397 | users = users_cloud 398 | 399 | elif lftype == 'cloud-cis': 400 | bootloader = bootloader_cloud_cis 401 | packages += packages_cloud_cis 402 | part = part_cloud_cis 403 | post = post_cloud_cis 404 | users = users_cloud_cis 405 | 406 | elif lftype == 'cis': 407 | bootloader = bootloader_cis 408 | packages += packages_cis 409 | part = part_cis 410 | post = post_cis 411 | users = users_cis 412 | 413 | elif lftype == 'minimal': 414 | bootloader = bootloader_minimal 415 | packages += packages_minimal 416 | part = part_minimal 417 | post = post_minimal 418 | users = users_minimal 419 | 420 | # version specifics 421 | with open('/etc/redhat-release') as file: 422 | rhel_version = int(re.findall('[0-9]{1,2}', file.read())[0]) 423 | 424 | print('Linuxfabrik Kickstart: Detected OS version {}'.format(rhel_version)) 425 | if rhel_version == 7: 426 | try: 427 | packages.remove('dhcp-client') # does not exist on RHEL7 428 | except ValueError: 429 | pass # means it is already removed 430 | 431 | print('Linuxfabrik Kickstart: Write dynamic.ks') 432 | dynamic = [] 433 | keypost = [] 434 | 435 | dynamic.append('# System users') 436 | for user in users: 437 | dynamic.append(user['cmd']) 438 | for key in user['keys']: 439 | if rhel_version == 7: # sshkey only exists for RHEL8+. instead manually add the keys 440 | if user['name'] == 'root': 441 | keypost.append('mkdir -m0700 /root/.ssh') 442 | keypost.append('echo "{}" >> /root/.ssh/authorized_keys'.format(key)) 443 | keypost.append('restorecon -F /root/.ssh/authorized_keys') 444 | else: 445 | keypost.append('mkdir -m0700 /home/{}/.ssh'.format(user['name'])) 446 | keypost.append('echo "{}" >> "/home/{}/.ssh/authorized_keys"'.format(key, user['name'])) 447 | keypost.append('chown -R {}: /home/{}/.ssh'.format(user['name'], user['name'])) 448 | keypost.append('restorecon -F "/home/{}/.ssh/authorized_keys"'.format(user['name'])) 449 | else: 450 | dynamic.append('sshkey --user={} "{}"'.format(user['name'], key)) 451 | 452 | dynamic.append('# Only touch $lfdisk. This setting is also respected by zerombr (confirmed on RHEL8 and 9)') 453 | dynamic.append('ignoredisk --only-use={}'.format(lfdisk)) 454 | 455 | dynamic.append('# System bootloader configuration') 456 | dynamic.append(bootloader) 457 | 458 | dynamic.append('# Do not remove any partitions, but initializes a disk (or disks) by creating a default disk label') 459 | dynamic.append('clearpart --all --drives={} --initlabel --disklabel gpt'.format(lfdisk)) 460 | 461 | dynamic.append('# Partitioning') 462 | dynamic.append('volgroup rl --pesize=4096 pv.0') 463 | dynamic.append('part pv.0 --fstype=lvmpv --ondisk={} --size=1 --grow'.format(lfdisk)) 464 | dynamic.append('part /boot --fstype=xfs --ondisk={} --size=1024 --asprimary'.format(lfdisk)) 465 | dynamic.extend(part) 466 | 467 | dynamic.append('%packages') 468 | dynamic.extend(packages) 469 | dynamic.append('%end') 470 | 471 | dynamic.append('%post --erroronfail') 472 | dynamic.append(post) 473 | dynamic.append('%end\n') 474 | 475 | # actually write to file 476 | with open('/tmp/dynamic.ks', 'w') as file: 477 | file.write('\n'.join(dynamic)) 478 | 479 | if rhel_version == 7: 480 | with open('/usr/share/anaconda/post-scripts/70-install-ssh-keys.ks', 'a') as file: 481 | file.write('%post\n') 482 | file.write('\n'.join(keypost)) 483 | file.write('%end\n') 484 | print('Linuxfabrik Kickstart: Wrote dynamic kickstart to /tmp/dynamic.ks') 485 | EOT 486 | $PYTHON /tmp/pre-script.py 487 | %end 488 | 489 | 490 | 491 | %post --nochroot 492 | root_mount=$(awk '/rl-root/{print $2}' /proc/mounts) 493 | echo "Copying /tmp/dynamic.ks to $root_mount/root/" 494 | cp /tmp/dynamic.ks $root_mount/root/ 495 | [ -f /usr/share/anaconda/post-scripts/70-install-ssh-keys.ks ] && cp /usr/share/anaconda/post-scripts/70-install-ssh-keys.ks $root_mount/root/ 496 | %end 497 | --------------------------------------------------------------------------------