Creating Rocky Linux 10.1 for 32bit i686/i586

[Screenshot of Rocky Linux 10.1 desktop on Pentium 120MHz]

This page is an attempt to create a Rocky Linux 10 for 32bit i686/i586.

Note: This is just a technical challenge. It is insanely impractical to deploy Rocky Linux/AlmaLinux/CentOS Stream 10 on sub-GHz CPUs in any way.

It works fairly well on Pentium M 1.8GHz. KDE Plasma 6 is somewhat sluggish, but works.

Some figures deployed on Pentium 120MHz:


$Keywords: Rocky Linux 10, AlmaLinux 10, CentOS Linux 10, 32bit, el10.i686, el10.i586, installation media respin, anaconda, Pentium, CMOV emulation, SSE emulation, SSE2 emulation, QtWebEngine without SSE $

$Id: i686.html,v 1.13 2026-06-19 08:47:16+09 kabe Exp kabe $ (2026/06)


Memory Requirement

You need enough memory to fit the unexpanded (115MB) and expanded (~202MB) initrd of anaconda installer, plus kernel itself (29MB uncompressed), plus the work memory. 640MB was not enough.

anaconda installer is written in Python, which inherently hogs a lot of memory.

BTW x86_64 needs at least 1280MB of memory to invoke the installer. 8 byte pointers do take much memory.

Video card requirement

X.org support was dropped in RHEL 10. Xorg bundled on this site is a recompile of it in Fedora 43. Non-DRM videocard (such as S3 Virge) is unfortunately out of scope. You could still install with Remote Desktop mode by giving inst.graphical inst.rdp kernel option. You could then additionally install Xorg for legacy hardware.


Things you need


Preparing i686 compilation environment

Compiling for .i586.rpm packages should be done in setarch i686'ed chroot environment. Many packages just doesn't cross-compile properly in x86_64 environment.

On the work machine, make SELinux permissive. This is a lorax(1) requirement. You need a reboot for this. Build of sed package fails if SELinux is totally disabled.

Bind-mounting a chroot filesystems

Suppose you are prepring a chroot environment in /chroot/i686/ of the work machine. After sudo mkdir -p /chroot/i686/, add the following entries in /etc/fstab :

# /chroot/i686
/proc		/chroot/i686/proc	none auto,bind 0 0
/dev		/chroot/i686/dev	none auto,bind 0 0
/dev/pts	/chroot/i686/dev/pts	none auto,bind 0 0
tmpfs		/chroot/i686/dev/shm	tmpfs auto,nosuid,nodev 0 0
sysfs		/chroot/i686/sys	sysfs auto,rw,nosuid,nodev,noexec 0 0
/sys/fs/cgroup	/chroot/i686/sys/fs/cgroup none auto,bind 0 0
## to let systemd build test see a /sys/fs/cgroup/systemd fs_type
/sys/fs/cgroup/systemd	/chroot/i686/sys/fs/cgroup/systemd none auto,bind 0 0
## to let systemd build test see /run/systemd/session/<#>
/run		/chroot/i686/run	none auto,bind 0 0
## to let sed build test see selinux
selinuxfs	/chroot/i686/sys/fs/selinux	selinuxfs auto 0 0

/home		/chroot/i686/home	none auto,bind 0 0

Do:

base$ sudo mkdir -p /chroot/i686/{proc,dev,sys,run} /chroot/i686/home

Then, manually mount the entries above, or just mount -a to mount them all.

Bootstrapping 32bit development environment in the chroot

The goal is to prepare enough 32bit packages into /chroot/i686/ to run rpmbuild(8).

Drop-in ix86-vega.repo as /chroot/i686/etc/yum.repos.d/ix86-vega.repo .

base$ sudo mkdir -p /chroot/i686/etc/yum.repos.d/
base$ sudo cp ix86-vega.repo /chroot/i686/etc/yum.repos.d/ix86-vega.repo
base$ sudo cp -p /etc/resolv.conf /chroot/i686/etc/
base$ sudo cp -p /etc/passwd /etc/shadow /etc/group /chroot/i686/etc/

Install rpm-build package and mass dependencies using dnf --installroot . Use precompiled packages provided by this site for bootstrapping.

base$ sudo setarch i686 \
    dnf --installroot=/chroot/i686 --releasever=10 \
    --disablerepo=\* \
    --enablerepo=ix86repo\* \
    --verbose install rpm-build sudo

Pull in other packages needed for rpmbuild:

base$ sudo setarch i386 \
    dnf --installroot=/chroot/i686 --releasever=10 \
    --disablerepo=\* \
    --enablerepo=ix86repo\* \
    --verbose install dnf gcc make vi util-linux

chrooting to the i686 environment

With bash(1) installed in the chroot, it should be able to chroot inside. Check uname -a to see if it is an i686 environment.

base$ uname -a
Linux rocky10.five.ten 6.12.0-124.52.1.el10_1.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Apr 23 13:41:41 UTC 2026 x86_64 GNU/Linux
base$ sudo setarch i686 chroot /chroot/i686 su - $LOGNAME
i686$ uname -a
Linux rocky10.five.ten 6.12.0-124.52.1.el10_1.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Apr 23 13:41:41 UTC 2026 i686 GNU/Linux
i686$ _
This is tedious, so you want to add the following to ~/.bashrc in the work machine's base x86_64 system:
if [ `uname -m` != i686 ]; then
	alias i686env="sudo setarch i686 chroot /chroot/i686 su - $LOGNAME"
else
	PS1="[\u@i686 \W]\$ "
fi


Compiling a Package

Now, you would compile the individual packages.

To compile a package,

mkdir a dedicated directory

Since you are mass-building various packages, directly building under ~/rpmbuild/ is not recommended. The example below uses ~/r10builds/coreutils-r10/ for coreutils package build.

base$ sudo setarch i686 chroot /chroot/i686 su - $LOGNAME
user@i686$ cd
user@i686$ mkdir -p r10builds/coreutils-r10
user@i686$ cd r10builds/coreutils-r10
user@i686$ pwd
/home/user/r10builds/coreutils-r10

Prepare the directory for rpmbuild

You would like to run a following mkrpmdir shellscript inside the directory:

#!/bin/sh

### mkdir
for d in SPECS SOURCES; do mkdir -p $d; done

### ./rpmbin
cat > ./rpmbin << 'EOF'
#!/bin/sh

D=${0%/*}
## bash builtin "pwd" will return virtual path when
## symlink involves in the path. Avoid it.
## Otherwise /usr/lib/rpm/debugedit will be screwed.
test x"$D" = x"." && D="`/bin/pwd`"

#--target=i586
exec ${0##*/} -D "_topdir $D" "$@"

EOF
chmod +x ./rpmbin

### symlink ./rpmbuild, ./rpm -> rpmbin
for i in rpmbuild rpm; do rm -f ./$i; ln -s rpmbin ./$i; done

### Makefile
if [ ! -e ./Makefile ]; then
cat > ./Makefile << 'EOF'
PKG=$(shell ls SPECS/*.spec | sed -ne 's:SPECS/\(.*\)\.spec$$:\1:p')
DIST=.el10
binary:
	uname -a > log
	CPPFLAGS="-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 -fcf-protection=none" \
	./rpmbuild --target=i586 -v -bb --noclean \
	-D 'dist $(DIST)' \
	SPECS/$(PKG).spec 2>&1 | \
	while IFS="" read line; do echo `date '+%Y-%m-%d %T'` "$$line"; done | \
	tee -a log

src:
	./rpmbuild --target=i586 -v -bs --noclean \
	-D 'dist $(DIST)' \
	SPECS/$(PKG).spec
EOF
fi
This script prepares ./rpm, ./rpmbuild and sample ./Makefile to install/compile the package.

Download a source rpm

Source RPMs are available under repo/Source/, or if not, under Rocky Linux download site. For example, source RPM for coreutils package is at https://ftp.iij.ad.jp/pub/linux/rocky/10.1/BaseOS/source/tree/Packages/c/coreutils-9.5-6.el10.src.rpm . Download it:

user@i686$ curl -R -O https://ftp.iij.ad.jp/pub/linux/rocky/10.1/BaseOS/source/tree/Packages/c/coreutils-9.5-6.el10.src.rpm
You would like to take note what directory the package goes eventually:
user@i686$ echo BaseOS > reponame

Expand the source rpm in the directory

"Install" the source rpm in the current directory. You do not need a root privilege for source installation and compile.

user@i686$ ./rpm -ivh coreutils-9.5-6.el10.src.rpm
Note that you use ./rpm . It installs the source RPM in current directory and populates ./SPECS/ and ./SOURCE/ .

Invoke rpmbuild to build the binary RPM

You will invoke ./rpmbuild multiple times until it compiles, so ./Makefile template is provided by above mkrpmdir script.

Change the DIST=.el10 part according to the package.

Invoke rpmbuild to build the binary RPM

You have made a Makefile, so invoke

[kabe@i686 coreutils-r10]$ make
uname -a > log
LANG=C.utf-8 \
CPPFLAGS="-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 -fcf-protection=none" \
./rpmbuild --target=i586 -v -bb --noclean \
-D 'dist .el10' \
SPECS/coreutils.spec 2>&1 | \
while IFS="" read line; do echo `date '+%Y-%m-%d %T'` "$line"; done | \
tee -a log
2026-05-25 12:30:09 Building target platforms: i586
2026-05-25 12:30:09 Building for target i586
2026-05-25 12:30:09 setting SOURCE_DATE_EPOCH=1732579200
2026-05-25 12:30:09 error: Failed build dependencies:
2026-05-25 12:30:09     acl is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     autoconf is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     automake is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     gdb is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     gettext-devel is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     glibc-langpack-en is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     glibc-langpack-fr is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     glibc-langpack-ko is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     glibc-langpack-sv is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     gmp-devel is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     gnupg2 is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     hostname is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     libacl-devel is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     libattr-devel is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     libcap-devel is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     libselinux-devel is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     libselinux-utils is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     openssl-devel is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     perl(FileHandle) is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     perl-interpreter is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     strace is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     texinfo is needed by coreutils-9.5-6.el10.i586
2026-05-25 12:30:09     valgrind is needed by coreutils-9.5-6.el10.i586
Log will be logged in ./log file. You will normally have dependencies like above to resolve for building packages. Recursively build and install them, or install from this site:
i686# dnf --disablerepo=\* --enablerepo=ix86repo-\* install acl
Repeat recursive build, install and make until binary RPMs are built.

The first package you will compile will be gcc, to obtain libgcc.so . Next will be glibc . Both is already available from this site.

Opcode emulation kernel

The kernel has CMOV, NOPL, FCOMI, FCMOVcc opcode emulation, and some SSE2 emulation enough to run compiler-generated (-mfpmath=sse) SSE2 instructions. This will let .i686 binaries run on i586 CPUs, which lacks cmov and sse2 capability.

Note: opcode emulation is very slow in nature. Recompile for .i586 whenever possible.

Counts of emulated opcodes will be available under /proc/emulated_ops:

$ cat /proc/emulated_ops
cmov:   4408415
nopl:   0
fcomi:  0
fucomi: 68
fcmov:  0
sse:    55453
sse2:   1178191
sse3:   10
endbr32: 234098

Patched kernel

Patch for emulating SSE2 by kernel Is available. Dig into Source RPMs for latest patches.

Patched QtWebEngine

Patch for QtWebEngine to avoid SSE2 dependency, is available. Dig into Source RPMs for latest patches. Note that you still need SSE emulation kernel, since V8 JavaScript engine REQUIRES %xmm SSE registers.

GNOME3 is heavy; use KDE5

From RHEL 9, for desktop environment, only GNOME 3 is provided, but on slow machines I strongly recommend KDE. GNOME3 had become too heavyweight for sub-GHz, single-thread processor. KDE Plasma 6 is provided via EPEL, but since it provides only x86_64 binaries, every KDE 6 components had to be recompiled for 32bit.


Packages needed to be copied from Rocky Linux repository

Collect .noarch packages listed in rocky-packages.txt from your favorite Rocky Linux x86_64 repository or mirror. For downloading, dnf download --downloadonly --downloaddir=`pwd` package will download it in the current directory, but timestamp is not preserved. Recommend downloading by wget or curl -R -O .


Packages needed to be compiled

Compile source packages listed in compile-packages.txt .

These packages needs to be compiled because either

Use Source RPMs in Source directory if available. If there wasn't, use Source RPMs from Rocky Linux download site.

EPEL packages for KDE Plasma 6

For KDE Plasma 6 desktop, additional EPEL source packages must be compiled.

Modifying Makefile for Y2038 compliance

Inspect the SPECS/*.spec file.

If %build begins with %configure or %meson

Just passing CPPFLAGS="-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 -fcf-protection=none" as environment value to rpmbuild is often enough.

If %build begins with %{qmake_qt5}
Pass %define to ./rpmbuild:
./rpmbuild --target=i586 -v -bb \
-D "_qt5_cflags -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 -fcf-protection=none" \
-D "_qt5_cxxflags -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 -fcf-protection=none" \
-D 'dist $(DIST)' 
	

If %build begins with %cmake , or direct %make_build

You must modify *.spec with CFLAGS before invocation, as:

+%bcond_with use_time_bits64
 ...
 %build
+
+%if %{with use_time_bits64}
+export CFLAGS="${CFLAGS:-%{build_cflags}} -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64"
+export CXXFLAGS="${CXXFLAGS:-%{build_cxxflags}} -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64"
+%endif
+%ifarch i586
+export CFLAGS="${CFLAGS:-%{build_cflags}} -fcf-protection=none"
+export CXXFLAGS="${CXXFLAGS:-%{build_cxxflags}} -fcf-protection=none"
+%endif
+
 %cmake
	
and invoke rpmbuild with --with use_time_bits64 option.

Other cases:
There are few other cases; differs on what environment variable the *.spec files touches with.

Collecting Compiled Packages into a Repository

You need to collect the downloaded/compiled RPMs and assemble them as a repository, which lorax(1) requires.

Preparing a Repository directory

Prepare another directory for a repository:

user@i686$ cd
user@i686$ mkdir ix86.repo
user@i686$ cd ix86.repo
user@i686$ mkdir -p BaseOS/Packages AppStream/Packages CRB/Packages devel/Packages epel/Packages

Collecting compiled RPMs

Then, collect the downloaded/compiled binary RPMs into the above directories. You don't have to strictly follow the location of the directory as published in Rocky/RHEL; if you are lazy, just cramming everything into BaseOS/Packages/ may work.

user@i686$ find ../r10builds/coreutils-r10/RPMS/ -name '*.rpm' | egrep -v -- '-debuginfo-|-debugsource-' | xargs ln -t BaseOS/Packages
You would like to take advantage of "reponame" file made earlier for automatic sieving.

Run createrepo_c

First, browse in the Rocky Linux x86_64 binary repository repodata/ for comps.xml file. If your favorite mirror is located at https://download.rockylinux.org/pub/rocky/10.1/BaseOS/x86_64/os/ , the comps.xml file may be at https://download.rockylinux.org/pub/rocky/10.1/BaseOS/x86_64/os/repodata/6babe543df2338047594fa6732cdc979774319195f01aaefeff4d99f0ae383f9-comps-BaseOS.x86_64.xml . Copy it as ./comps-BaseOS.x86_64.xml (for later use).

Then, invoke createrepo_c against the RPM collection directory:

user@i686$ mkdir -p ./BaseOS/repodata/
user@i686$ cp -p comps-BaseOS.x86_64.xml ./BaseOS/repodata/comps.xml
user@i686$ createrepo_c -v --groupfile repodata/comps.xml ./BaseOS
Do the same thing against ./AppStream/ , ./CRB/ , ./devel/ and ./epel/ directory.


Assembling anaconda installer

After you had made the repository, it's the time to use lorax(1) to assemble the anaconda installer.

Install lorax package from official repository or media.

$ sudo dnf --disablerepo=\* --enablerepo=media\* install lorax

If you are invoking lorax on base x86_64 machine, you must patch lorax, and patch python3-productmd, beforehand.

Create boot image with lorax

Prepare a separate directory for boot image assembling. Begin with a pristine directory to store the boot.iso file:

base$ cd
base$ mkdir r10lorax
base$ cd r10lorax

base$ rm -fr ./img10

Download add_template.tmpl file. You need this if the target machine is a slow machine.

Then, invoke the lorax with some options. Below assumes that assembled repository resides in ../ix86.repo/ .


base$ LANG=C sudo lorax -p "Rocky Linux" -v 10.1 -r 10.1 \
	-s `pwd`/../ix86.repo/BaseOS \
	-s `pwd`/../ix86.repo/AppStream \
	-s `pwd`/../ix86.repo/CRB \
	--add-template `pwd`/add_template.tmpl \
	--buildarch=i686 --nomacboot \
	`pwd`/img10
This takes a about 15 minutes on 3GHz-4thread machine. Logfiles are automatically created as ./lorax.log, ./pylorax.log, ./program.log .

As a result, ./img10/ will be populated with installer boot files, notably ./img10/images/boot.iso .

Scratch directories, owned by root, may be lying around in /var/tmp/lorax/ directory. You can safely delete them.

The created files are owned by root, so you would like to

base$ sudo chown -R $LOGNAME ./img10
base$      chmod -R +w       ./img10

The generated ./img10/images/boot.iso of about 768MB should boot as a network installer. You would like to try it out on target machine to see if the anaconda installer would work. (from RHEL 8.5, even the network installer does not fit on a CD.)

Discussion

lorax does not use the native files of the work machine to build the installer; it unpacks files from the *.rpm in the -s path directory. Thus the work host doesn't have to be i686; working on x86_64 should be okay.


Collect packages from the repository to a DVD tree

Copy (hardlink) over your repository contents to ./img10/ .

base$ sudo chown -R $LOGNAME ./img10
base$ rm -fr ./img10/Packages ./img10/SPackages ./img10/repodata
base$ rm -fr ./img10/{BaseOS,AppStream,CRB,devel,epel}
base$ cp -rpl ../ix86.repo/{BaseOS,AppStream,CRB,devel,epel} ./img10/

Optionally, if the target machine is slow, append the kernel boot line with inst.xtimeout=600 (wait for 600 seconds for Wayland to start; default 60 secs)

base$ sed -i -e 's/^\([ 	]linux .*\) quiet$/\1 inst.xtimeout=600 inst.disklabel=mbr/' ./img10/boot/grub2/grub.cfg


Regenerate .treeinfo

The ./img10/.treeinfo generated by lorax doesn't mention BaseOS/, AppStream/ et al directives. You must recreate it.

You need to install python3-productmd package beforehand. If the work host is not Fedora, RHEL or CentOS, you have to patch /usr/lib/python3.12/site-packages/productmd/treeinfo.py .

Download productmd-treeinfo.py file, and invoke as:

base$ if [ ! -e ./img10/.treeinfo.lorax ]; then cp -p ./img10/.treeinfo ./img10/.treeinfo.lorax; fi
base$ python3 ./productmd-treeinfo.py ./img10/.treeinfo.lorax > ./img10/.treeinfo


Respin the media

Now you can respin the DVD media image.

base$ rsync -avH /usr/lib/grub/i386-pc ./img10/boot/grub2/
base$ xorrisofs -o ./DVD1.iso \
	-R -J \
	-V "Rocky-Linux-10-1-i386" \ 
	--grub2-mbr /usr/lib/grub/i386-pc/boot_hybrid.img \
	-partition_offset 16 -appended_part_as_gpt \
	-b images/eltorito.img -c images/boot.cat \
	-no-emul-boot -boot-load-size 4 -boot-info-table \
	--grub2-boot-info -eltorito-alt-boot \
	-v \
	-m upgrade.img -m boot.iso \
	-m 'texlive-*' \
	-m 'mingw*' -m 'gcc-toolset-12-*' -m 'gcc-toolset-13-*' \
	-m 'java-11-openjdk-src-*' \
	-m 'java-11-openjdk-*-devel-*' \
	-m 'java-11-openjdk-*-slowdebug-*' \
	-no-pad -iso-level 3 -D --hardlinks -joliet-long \
	./img10
base$ implantisomd5 ./DVD1.iso

The volume label (-V "Rocky-Linux-10-1-i386") should match with kernel option in img10/boot/grub2/grub.cfg , inst.stage2=hd:LABEL=Rocky-Linux-10-1-i386 .
Excluding boot.iso (~800MB) and texlive-* (~480MB) is for lowering size of the final DVD1.iso .

This will create a DVD1.iso image around 5.7GB. Burn the DVD1.iso and try it out on your target machine. You need to adjust xorrisofs -m [packages-to-exclude] to fit them onto 4.7GB DVD-RW.


Installing

From RHEL 10, Xorg is deprecated and Wayland is the default graphics engine. If your target machine does not properly support Wayland, that is, when booting by the media results in

You have to resort to invoke the installer in Remote Desktop Protocol mode. On the grub2 boot screen, press [e] and add inst.graphical inst.rdp to the kernel options. When the anaconda installer starts, it will prompt you for RDP username and password of a choice.

This means you need a wired network card installed on the target machine; anaconda installer is not aware of a wireless network.


Troubleshooting

in separate page.
kabe.sra-tohoku.co.jp