Create AlmaLinux ISO that contains the rpm files and installation gui does not need internet connection

Hello all, I am trying to create an ISO from AlmaLinux, which will have the gui installer and this ISO would have all the rpm files, packages and dependencies needed when I select a specific installation environment in the installation screen.

The end result should be an ISO, that when I insert it on a new machine that does not have internet connection, I would be able to boot from the ISO, start the gui installation, select the environment, e.g.
“Workstation” or “KDE” and then the installer would install the corresponding packages from the ISO into that machine, not from the internet.

I have found a guide that uses a Kickstart file, I updated it and it works for a gui installation but the packages are downloaded from the internet (from some AlmaLinux package manager server), the required packages are not included in the ISO itself.

I used this guide: Customizing the AlmaLinux Installer Media – One Zero One
and I did some changes in this Kickstart file: Example AlmaLinux Kickstart – One Zero One

I also tried to include in the ks.cfg the lines:
repo=cdrom or
repo --name=dvd --baseurl=file://Packages

but it does not work, which is expected

On CentOS 7 I used Pungi for this, which checks for all packages and dependencies needed and it includes them in the final ISO, but on AlmaLinux this does not work. Could you please provide some help?

Thank you!

Hi @ioannisgk ,

I have similar needs, and I have arrived at the following solution, which works with all of the EL clones that I have tried it with so far.

  1. download the DVD image. It is large – too big even for a dual-layer DVD, so use the torrent if you need to.
  2. Create a bootable USB from this image. I used Rufus on Window$ 10 (I had some issues with inconsistent behavior with dd). There is a lot of variance in the tools out there, so your luck may vary. I also use this to create a persistent partition similar to live media, which is important if you need a kickstart file.
  3. This should boot into the Anaconda installer GUI. This may be all that you need to do, depending on your bios.
  4. If your bios sees the install media as sda, you will need to create a kickstart to set up the media source and bootloader sections. I do this with a small python script as the %pre script to create the partition section of kickstart, which is then included before the install begins. I will include both this script and a kickstart that I have used to install at the end of this post.
  5. If you do need a kickstart, you will need to be able to tell the install kernel where it is, which is when another linux system is essential. You can do it from the rescue shell, but it is much more involved, especially since you will need to place you kickstart file on the media eventually. Break into the grub2 menu to edit the kernel line, adding the following options:
    inst.repo=hd:label=ALMALINUX-8 inst.ks=hd:label=persistence:/ks.cfg inst.gpt
    The first option gives the location of the repo as a ISO image, the second is the location of the kickstart file, and the third prefers the use of gpt partitioning, which I have found is absolutely necessary, especially if you plan on hardening the system.
    You can edit the EFI/BOOT/grub.cfg file to put these options into a menuentry, if this media is going to be used to configure multiple systems. Simply copy the 4 lines that make up an entry, and add the options to the linuxefi line.

Hope this helps. – faitjx

Section of a kickstart file, giving most of what I talked about here. It does not set up the network or hostname. The installation media is a 16 GiB Kingston USB thumb drive.

#version=RHEL8
#
eula --agreed
# Use graphical install
graphical 
# partitioning file loaded here -- generated in %pre
%include /tmp/parts.cfg
#  Installation sources 
harddrive  --partition=LABEL=ALMALINUX-8 --dir=/
repo --name="AppStream" --baseurl=file:///run/install/repo/AppStream
#
# Keyboard layouts
keyboard --xlayouts='us'
# System language
lang en_US.UTF-8
%packages
@^graphical-server-environment
@development
@network-file-system-client
@system-tools
kexec-tools
gnome-tweaks
python3-pip
-insights-client
%end

firstboot --enable

%anaconda
pwpolicy root --minlen=9 --minquality=30 --strict --nochanges --notempty 
pwpolicy user --minlen=9 --minquality=30 --strict --changesok --notempty
pwpolicy luks --minlen=9 --minquality=30 --strict --nochanges --notempty
%end

%pre --interpreter=/usr/libexec/platform-python
from pathlib import Path
lines =  Path('/dev/disk/by-id').glob('*')
devs = {}
for l in lines:
	s=l.as_posix()
	if s.startswith('/dev/disk/by-id/wwn-') and (not s.endswith('-part',-6,-1)):
		d = l.resolve().as_posix()
		devs[d] = s
	if s.startswith('/dev/disk/by-id/usb-Kingston') and (not s.endswith('-part',-6,-1)):
		source = l.as_posix()
fd = open('/tmp/parts.cfg',mode='w')
klst  = list(devs.keys())
klst.sort()
dest=devs.get(klst[0])
print('ignoredisk --drives=%s'%(source),file=fd)
print('bootloader --location=mbr   --drive=%s'%(dest),file=fd)
print('clearpart --all --initlabel --disklabel=gpt --drives=%s'%(dest),file=fd)
# Disk partitioning information
print('part biosboot --fstype="biosboot" --size=2 --ondisk=%s'%(dest),file=fd)
print('part /boot/efi --fstype="efi" --size=400 --ondisk=%s'%(dest),file=fd)
print('part swap  --fstype="swap" --hibernation  --ondisk=%s'%(dest),file=fd)
print('part /boot --fstype="xfs" --label=BOOT --size=2000   --ondisk=%s'%(dest),file=fd)
print('part /     --fstype="xfs" --label=ROOT --size=100000 --ondisk=%s'%(dest),file=fd)
print('part /var  --fstype="xfs" --label=VAR  --size=100000 --ondisk=%s'%(dest),file=fd)
print('part /opt  --fstype="xfs" --label=OPT  --size=100000 --ondisk=%s'%(dest),file=fd)
print('part /home --fstype="xfs" --label=HOME --size=100000 --grow --ondisk=%s'%(dest),file=fd)
fd.close()
%end