This is a short update on my Linux OS distribution project (I don’t know the final purpose of this distro yet; for now, it’s just for training).
My latest update was creating a .img file, which I run through Cockpit (using https://github.com/cockpit-project/cockpit-machines). Now, I would like to create a bootable operating system by generating an ISO image.
This process takes the previously created root filesystem, which contains a minimal structure to be executed.
root@ns3137793:/mnt/lfs# du -chs *
0 bin
28M boot
8.0K build
0 dev
4.0K dist
17M etc
4.0K home
0 lib
4.0K lib64
12K media
4.0K mnt
4.0K opt
4.0K proc
1.5M root
44K run
0 sbin
4.0K srv
4.0K sys
148K tmp
2.2G usr
84K var
Instead of using prepare_image.sh (https://github.com/lucky-sideburn/generic-distro-toolkit/blob/main/jenkins-lfs/playbooks/roles/ansible-gdt/files/prepare_image.sh), I need to create a prepare_iso.sh shell script. The initial concept is to maintain the entire root filesystem structure while creating custom grub.cfg, inittab, and fstab files.
# This is a temporary folder. I want to avoid corrupting my original /mnt/lfs
ISO_WORKSPACE="/tmp/lfs_iso_ws"
sudo mkdir -p $ISO_WORKSPACE
# I copy everything from /mnt/lfs but excluding sources directory
# Use -a to preserve permissions/symlinks which are critical for LFS
sudo rsync -a --progress --exclude='/mnt/lfs/sources' --exclude='/mnt/lfs/build' /mnt/lfs/ $ISO_WORKSPACE/
# Copy the Kernel
sudo cp /mnt/lfs/boot/vmlinuz-6.13.4-lfs-12.3 $ISO_WORKSPACE/boot/vmlinuz
# Create the GRUB config inside the workspace
# This is a live iso so my root is /dev/sr0
# I want to have tty console and serial console so I define both console=ttyS0,115200 console=tty1
# Use InitramFS
sudo mkdir -p $ISO_WORKSPACE/boot/grub
sudo tee $ISO_WORKSPACE/boot/grub/grub.cfg << EOF
set default=0
set timeout=10
menuentry "DevOpstribe Linux" {
linux /boot/vmlinuz root=/dev/sr0 ro rootfstype=iso9660 init=/sbin/init console=ttyS0,115200 console=tty1
initrd /boot/initrd.img-6.13.4
}
EOF
# I create an inittab file to define 3 as default runlevel (for production system use SystemD :D)
# Please pay attention to 1:2345:respawn:/sbin/agetty --autologin root --noclear -n tty1 9600, I do not want login with username and password but I want to start the installer script directly after the boot process
sudo tee $ISO_WORKSPACE/etc/inittab << 'EOF'
# Default Runlevel
id:3:initdefault:
# System initialization
si::sysinit:/etc/rc.d/init.d/rc S
# What to do in single-user mode
~:S:wait:/sbin/sulogin
# What to do when CTRL-ALT-DEL is pressed
ca::ctrlaltdel:/sbin/shutdown -t1 -a -r now
# Runlevels
l0:0:wait:/etc/rc.d/init.d/rc 0
l1:1:wait:/etc/rc.d/init.d/rc 1
l2:2:wait:/etc/rc.d/init.d/rc 2
l3:3:wait:/etc/rc.d/init.d/rc 3
l4:4:wait:/etc/rc.d/init.d/rc 4
l5:5:wait:/etc/rc.d/init.d/rc 5
l6:6:wait:/etc/rc.d/init.d/rc 6
# Consoles
1:2345:respawn:/sbin/agetty --autologin root --noclear -n tty1 9600
2:2345:respawn:/sbin/agetty tty2 9600
3:2345:respawn:/sbin/agetty tty3 9600
# End of /etc/inittab
EOF
sudo tee $ISO_WORKSPACE/etc/fstab << 'EOF'
# Begin /etc/fstab for Live ISO
# file system mount-point type options dump fsck
proc /proc proc nosuid,noexec,nodev 0 0
sysfs /sys sysfs nosuid,noexec,nodev 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
tmpfs /run tmpfs defaults 0 0
devtmpfs /dev devtmpfs mode=0755,nosuid 0 0
tmpfs /dev/shm tmpfs nosuid,nodev 0 0
cgroup2 /sys/fs/cgroup cgroup2 nosuid,noexec,nodev 0 0
# CD-ROM is already mounted as root, no need to mount it again
# /dev/sr0 / iso9660 ro 0 0
# End /etc/fstab
EOF
sudo rm $ISO_WORKSPACE/etc/rc.d/rcS.d/S45cleanfs
sudo rm $ISO_WORKSPACE/etc/rc.d/rcS.d/S40mountfs
echo "devopstribe-linux" | sudo tee $ISO_WORKSPACE/etc/hostname
# Also update /etc/hosts
sudo tee $ISO_WORKSPACE/etc/hosts << 'EOF'
# Begin /etc/hosts
127.0.0.1 localhost.localdomain localhost
127.0.1.1 lfs-live.localdomain lfs-live
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
# End /etc/hosts
EOF
# Make sure hostname is set at boot
# Check if you have a hostname init script
ls -l $ISO_WORKSPACE/etc/rc.d/init.d/hostname
# If it doesn't exist, create one
sudo tee $ISO_WORKSPACE/etc/rc.d/init.d/hostname << 'EOF'
#!/bin/sh
########################################################################
# Begin hostname
#
# Description : Set hostname
#
########################################################################
. /lib/lsb/init-functions
case "${1}" in
start)
log_info_msg "Setting hostname..."
hostname -F /etc/hostname
evaluate_retval
;;
*)
echo "Usage: ${0} {start}"
exit 1
;;
esac
# End hostname
EOF
sudo cp /mnt/lfs/sources/system-installer.sh $ISO_WORKSPACE/usr/local/bin/system-installer.sh
sudo chmod +x $ISO_WORKSPACE/usr/local/bin/system-installer.sh
> $ISO_WORKSPACE/root/.bashrc
> $ISO_WORKSPACE/root/.bash_profile
sudo tee -a $ISO_WORKSPACE/root/.bashrc << 'EOF'
#!/bin/bash
# 1. Stop kernel messages
dmesg -n 1
# Restore terminal settings
stty sane
# 2. Clear the screen completely
clear
# 4. Optional: Restore kernel logging on exit
dmesg -n 7
# Auto-start installer on first login
# It is important to use "exec" because we don't want that the user exit from the installer script
if [ -f /usr/local/bin/system-installer.sh ]; then
exec /usr/local/bin/system-installer.sh
fi
EOF
sudo tee -a $ISO_WORKSPACE/root/.bash_profile << 'EOF'
# Carica il bashrc se esiste
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
EOF
cat $ISO_WORKSPACE/root/.bash_profile
sudo chmod +x $ISO_WORKSPACE/etc/rc.d/init.d/hostname
# Link it to run early in boot
sudo ln -sf ../init.d/hostname $ISO_WORKSPACE/etc/rc.d/rcS.d/S02hostname
# 5. Generate the ISO
# WARNING: This ISO will be the size of your entire LFS install
sudo grub-mkrescue -o /var/lib/libvirt/images/lfs-system.iso $ISO_WORKSPACE -- -hfsplus off
sudo chown libvirt-qemu:kvm /var/lib/libvirt/images/lfs-system.iso
Now the system can boot from the lfs-system.iso image.

I want to provide a minimal graphical interface for my users, so I have chosen Dialog. It is very lightweight and is already used by several popular Linux distributions.
I will now add the Dialog compilation tasks to Jenkins by creating the job via Ansible. This is part of https://github.com/lucky-sideburn/generic-distro-toolkit that I use to automate some tasks.
- name: 0109 - Dialogs (Basic System Software) OPTIONAL
version: 2.15
archive_file: /sources/dialog.tar.gz
source_dir: /sources/dialog-1.3-20251223
description: |
Dialog is a tool for creating text-based user interfaces. This job builds Dialog, which is essential for managing file archives in the LFS environment.
category:
This job is part of the system configuration setup, specifically for building Dialog.
jenkins_job_url: http://localhost:8080/job/system_configuration/Dialogs-1.3-20251223
exec_command: echo
build_tool: true
build_command: |
{{ chroot_start_command }} -c '
cd /sources/dialog-1.3-20251223
./configure --prefix=/usr \
--with-ncursesw \
--enable-nls \
--with-libtool
make
make install
'

Once Dialogs is installed, you can use it at login configuring .bash_profile and .bashrc
sudo tee -a $ISO_WORKSPACE/root/.bashrc << 'EOF'
#!/bin/bash
# 1. Stop kernel messages
dmesg -n 1
# Restore terminal settings
stty sane
# 2. Clear the screen completely
clear
# 3. Run your dialog
dialog --msgbox "System Ready" 10 30
# 4. Optional: Restore kernel logging on exit
dmesg -n 7
# Auto-start installer on first login
if [ -f /usr/local/bin/system-installer.sh ]; then
#exec /usr/local/bin/system-installer.sh
/usr/local/bin/system-installer.sh
fi
EOF
sudo tee -a $ISO_WORKSPACE/root/.bash_profile << 'EOF'
# Carica il bashrc se esiste
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
EOF

