Build Forge Linux
From bare-metal toolchain to a named distribution you own — branded identity, package path, live ISO, installer, and release engineering. Host Fedora 44 · LFS 13.0-aligned packages · kernel 7.1.5.
Introduction
This guide is a spiritual successor to the Build Your Own Linux course originally offered by Linux Academy, updated for 2026 toolchains and modern system design. Like the original, it uses Fedora as the host system and walks you through building every component of a working Linux distribution from source code.
The approach is fundamentally the same as the venerable Linux From Scratch project — you compile everything yourself, understand every dependency, and end up with a minimal, bootable system. What this guide adds is Fedora-centric tooling context, package versions aligned with LFS 13.0-systemd (plus current kernel.org stable 7.1.5), and systemd as the init system from day one. Last refreshed 3 August 2026.
What You Will Build
By the end of this guide you will have Forge Linux — a named distro, not only a generic rootfs:
- Boots independently (partition or installable ISO)
- Shell, coreutils, util-linux, networking, systemd, GRUB 2.14
- Toolchain: glibc 2.43, GCC 15.2, kernel 7.1.5
- Full brand identity: os-release, MOTD, issue, hostname, GRUB theme
- Path to package manager, live ISO, installer, and release engineering (Ch. 20–28)
Philosophy
Chapters 00–19 build a correct minimal system (the LFS spine). Chapters 20–28 turn it into your distribution. You start with a learning system — one that gives you deep, hands-on understanding of how the pieces of Linux fit together. After completing this guide you will understand:
- What a cross-compilation toolchain is and why you need one
- The relationship between the kernel, glibc, and userspace
- How init systems, mount points, and the FHS all interrelate
- The role of the bootloader and how GRUB configures the kernel command line
- How distros claim identity (
/etc/os-release, branding, legal strings) - What separates a rootfs from a shippable distro (packages, ISO, release process)
Default brand used in Chapters 20–28. Rename via the identity kit — one file drives os-release, GRUB, MOTD, hostname, and ISO labels.
Time Estimate
On a modern machine with 4+ cores and an SSD, expect the full build to take 4–8 hours. The GCC and glibc builds dominate the compile time. Use make -j$(nproc) throughout to parallelise.
Conventions Used
Commands are shown in code blocks. The prompt tells you who should run the command:
root# command run as root
lfs$ command run as the lfs build user
# this is a comment / explanatory note
Variables you must substitute with your own values are written $LIKE_THIS.
Package Versions Reference
Always verify you are downloading the exact versions listed below. Using a different minor version can cause subtle build failures downstream, especially in the toolchain stages.
Toolchain
| Package | Version | Download | Role |
|---|---|---|---|
| Binutils | 2.46.0 | sourceware.org/binutils | Linker, assembler, object tools |
| GCC | 15.2.0 | gnu.org/gcc | C/C++ compiler collection |
| Linux (headers) | 7.1.5 | kernel.org | Kernel API headers for glibc |
| Glibc | 2.43 | gnu.org/glibc | GNU C library |
| GMP | 6.3.0 | gmplib.org | GCC dependency: arbitrary precision math |
| MPFR | 4.2.2 | mpfr.org | GCC dependency: floating point |
| MPC | 1.3.1 | multiprecision.org | GCC dependency: complex arithmetic |
Base System
| Package | Version | Download | Role |
|---|---|---|---|
| Bash | 5.3 | gnu.org/bash | Shell |
| Coreutils | 9.10 | gnu.org/coreutils | ls, cp, mv, cat, echo… |
| util-linux | 2.41.3 | kernel.org/util-linux | mount, blkid, lsblk, fdisk… |
| e2fsprogs | 1.47.3 | sf/e2fsprogs | ext4 filesystem tools |
| shadow | 4.19.3 | github/shadow | useradd, passwd, login |
| procps-ng | 4.0.6 | sf/procps-ng | ps, top, free |
| ncurses | 6.6 | invisible-island.net | Terminal UI library |
| readline | 8.3 | gnu.org/readline | Line editing (used by bash) |
| zlib | 1.3.2 | zlib.net | Compression library |
| bzip2 | 1.0.8 | sourceware.org/bzip2 | .bz2 compression |
| xz | 5.8.2 | tukaani.org/xz | .xz / LZMA compression |
| tar | 1.35 | gnu.org/tar | Archive tool |
| grep | 3.12 | gnu.org/grep | Pattern search |
| sed | 4.9 | gnu.org/sed | Stream editor |
| gawk | 5.3.2 | gnu.org/gawk | AWK interpreter |
| make | 4.4.1 | gnu.org/make | Build system |
| patch | 2.8 | gnu.org/patch | Apply diff patches |
| findutils | 4.10.0 | gnu.org/findutils | find, locate, xargs |
| systemd | 259.1 | github/systemd | Init system & service manager |
| GRUB | 2.14 | gnu.org/grub | Bootloader |
sha256sum <tarball> and compare against the checksums published on the project's official download page.
Preparing the Host System
We use Fedora 44 as the build host. The Fedora toolchain is modern and compatible with current package requirements. A minimal install plus development groups is all you need.
Install Required Host Packages
root# dnf groupinstall "Development Tools"
root# dnf install \
bison flex gawk texinfo bc m4 \
python3 perl wget curl \
libstdc++-static glibc-static \
xz bzip2 zlib-devel openssl-devel \
libmpc-devel gmp-devel mpfr-devel
Verify Tool Versions
Fedora 44 ships a modern toolchain (GCC 15 / Binutils 2.44+) that exceeds all minimums for this guide. Run this check to confirm:
root# bash -c '
echo "Bash: $(bash --version | head -1)"
echo "GCC: $(gcc --version | head -1)"
echo "Binutils: $(ld --version | head -1)"
echo "Make: $(make --version | head -1)"
echo "Python: $(python3 --version)"
'
Partition & Mount the Target Disk
We need a dedicated partition for the new system. For a VM build, add a second virtual disk (30 GB minimum) and partition it as follows.
Partition Layout
| Partition | Size | Filesystem | Mount |
|---|---|---|---|
| /dev/sdb1 | 512 MB | FAT32 (ESP) | /boot/efi (UEFI systems) |
| /dev/sdb2 | 1 GB | ext4 | /boot |
| /dev/sdb3 | Remainder | ext4 | / (root) |
root# fdisk /dev/sdb
# Create GPT partition table, then partitions as above
# Type g for GPT, n for new partition, w to write
root# mkfs.fat -F32 /dev/sdb1
root# mkfs.ext4 /dev/sdb2
root# mkfs.ext4 /dev/sdb3
Set the LFS Variable and Mount
The $LFS variable is used throughout — it points to where the new system is being assembled.
root# export LFS=/mnt/lfs
root# mkdir -pv $LFS
root# mount /dev/sdb3 $LFS
root# mkdir -pv $LFS/boot
root# mount /dev/sdb2 $LFS/boot
root# mkdir -pv $LFS/boot/efi
root# mount /dev/sdb1 $LFS/boot/efi
Create the Sources Directory
root# mkdir -v $LFS/sources
root# chmod -v a+wt $LFS/sources
# Download all source tarballs to $LFS/sources
# You can use wget with the URLs from Chapter 01
Build Environment Setup
Create the LFS Build User
Never build as root during the toolchain phase. Create a dedicated lfs user:
root# groupadd lfs
root# useradd -s /bin/bash -g lfs -m -k /dev/null lfs
root# passwd lfs
root# chown -v lfs $LFS/sources
root# chown -v lfs $LFS/tools
Configure the lfs User's Shell Environment
root# su - lfs
lfs$ cat > ~/.bash_profile << "EOF"
exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash
EOF
lfs$ cat > ~/.bashrc << "EOF"
set +h
umask 022
LFS=/mnt/lfs
LC_ALL=POSIX
LFS_TGT=$(uname -m)-lfs-linux-gnu
PATH=/usr/bin
if [ ! -L /bin ]; then PATH=/bin:$PATH; fi
PATH=$LFS/tools/bin:$PATH
CONFIG_SITE=$LFS/usr/share/config.site
export LFS LC_ALL LFS_TGT PATH CONFIG_SITE
EOF
lfs$ source ~/.bash_profile
set +h?
This disables bash's hash table so the shell always searches PATH fresh. This is critical during the toolchain build — we need the shell to pick up the new tools we're building in $LFS/tools rather than the cached host paths.
Create the Toolchain Directories
root# mkdir -pv $LFS/tools
root# ln -sv $LFS/tools /tools
The symlink /tools → $LFS/tools lets the toolchain build scripts use hardcoded /tools paths that work both on the host and, later, inside the chroot.
Binutils — Pass 1
Binutils provides the assembler (as), linker (ld), and object file utilities. It must be compiled first because GCC and glibc run configure checks that rely on it.
lfs$ cd $LFS/sources
lfs$ tar -xf binutils-2.46.0.tar.xz
lfs$ mkdir -v binutils-2.46.0/build
lfs$ cd binutils-2.46.0/build
lfs$ ../configure \
--prefix=/tools \
--with-sysroot=$LFS \
--target=$LFS_TGT \
--disable-nls \
--enable-gprofng=no \
--disable-werror \
--enable-new-dtags \
--enable-default-hash-style=gnu
lfs$ make -j$(nproc)
lfs$ make install
What --with-sysroot does
This tells binutils to look for libraries and headers under $LFS rather than the host root. Combined with --target=$LFS_TGT, this creates a cross-binutils that targets the new system.
GCC — Pass 1
The first GCC pass produces a minimal C-only cross-compiler. It is intentionally limited — no shared libraries, no C++ support. Its sole purpose is to compile glibc.
lfs$ cd $LFS/sources
lfs$ tar -xf gcc-15.2.0.tar.xz
lfs$ cd gcc-15.2.0
# Unpack GCC prerequisites into the source tree
lfs$ tar -xf ../gmp-6.3.0.tar.xz --transform 's/gmp-6.3.0/gmp/'
lfs$ tar -xf ../mpfr-4.2.2.tar.xz --transform 's/mpfr-4.2.2/mpfr/'
lfs$ tar -xf ../mpc-1.3.1.tar.gz --transform 's/mpc-1.3.1/mpc/'
# x86_64: limit library search to lib64 only
lfs$ case $(uname -m) in
x86_64)
sed -e 's/m64=/m64=/' \
-e 's@/lib/ld@/lib64/ld@g' \
-i gcc/config/i386/t-linux64
;;
esac
lfs$ mkdir -v build && cd build
lfs$ ../configure \
--target=$LFS_TGT \
--prefix=/tools \
--with-glibc-version=2.43 \
--with-sysroot=$LFS \
--with-newlib \
--without-headers \
--enable-default-pie \
--enable-default-ssp \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-threads \
--disable-libatomic \
--disable-libgomp \
--disable-libquadmath \
--disable-libssp \
--disable-libvtv \
--disable-libstdcxx \
--enable-languages=c,c++
lfs$ make -j$(nproc)
lfs$ make install
# Create a full-featured limits.h (needed for glibc build)
lfs$ cat gcc/limitx.h gcc/glimits.h gcc/limity.h > \
`dirname $($LFS_TGT-gcc -print-libgcc-file-name)`/include/limits.h
Linux Kernel Headers
The C library needs the kernel's API headers to know which system calls are available. We install them but do not build the kernel yet — that comes later.
lfs$ cd $LFS/sources
lfs$ tar -xf linux-7.1.5.tar.xz
lfs$ cd linux-7.1.5
lfs$ make mrproper
lfs$ make headers
lfs$ find usr/include -name '.*' -delete
lfs$ rm usr/include/Makefile
lfs$ cp -rv usr/include $LFS/usr
make mrproper ensures the source tree is pristine. Never skip this when working with kernel source trees.
Glibc — The GNU C Library
This is the most critical and time-consuming build step. Glibc is the interface between your programs and the Linux kernel. Every program on your system will be linked against it.
lfs$ cd $LFS/sources
lfs$ tar -xf glibc-2.43.tar.xz
lfs$ cd glibc-2.43
# Ensure the dynamic linker looks in /lib64 on x86_64
lfs$ case $(uname -m) in
i?86) ln -sfv ld-linux.so.2 $LFS/lib/ld-lsb.so.3 ;;
x86_64) ln -sfv /lib/ld-linux-x86-64.so.2 $LFS/lib64
ln -sfv /lib/ld-linux-x86-64.so.2 $LFS/lib64/ld-lsb-x86-64.so.3 ;;
esac
lfs$ patch -Np1 -i ../glibc-2.43-fhs-1.patch
lfs$ mkdir -v build && cd build
lfs$ echo "rootsbindir=/usr/sbin" > configparms
lfs$ ../configure \
--prefix=/usr \
--host=$LFS_TGT \
--build=$(../scripts/config.guess) \
--enable-kernel=5.4 \
--with-headers=$LFS/usr/include \
--disable-nscd \
libc_cv_slibdir=/usr/lib
lfs$ make -j$(nproc)
lfs$ make DESTDIR=$LFS install
# Fix the dynamic linker path in the GCC specs file
lfs$ sed '/RTLDLIST=/s@/usr@@g' -i $LFS/usr/bin/ldd
Sanity Check
This is critical. Test that the cross-compiler can actually produce executables that will run on the target:
lfs$ echo 'int main(){}' | $LFS_TGT-gcc -xc -
lfs$ readelf -l a.out | grep ld-linux
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
lfs$ rm -v a.out
/lib64/ or /lib/ — not the host's path. If it shows your host system's path, something went wrong in the glibc configure step. Do not proceed until this is correct.
Libstdc++ — Pass 1
Libstdc++ is GCC's C++ standard library. We install a pass-1 version now (from the same GCC source tree) so that subsequent packages with C++ components can build. The full version is reinstalled in GCC pass 2.
lfs$ cd $LFS/sources/gcc-15.2.0
lfs$ mkdir -v build-libstdc++ && cd build-libstdc++
lfs$ ../libstdc++-v3/configure \
--host=$LFS_TGT \
--build=$(../config.guess) \
--prefix=/usr \
--disable-multilib \
--disable-nls \
--disable-libstdcxx-pch \
--with-gxx-include-dir=/tools/$LFS_TGT/include/c++/15.2.0
lfs$ make -j$(nproc)
lfs$ make DESTDIR=$LFS install
# Remove the libtool archive files — they cause trouble later
lfs$ rm -v $LFS/usr/lib/lib{stdc++{,exp},supc++}.la
Binutils — Pass 2
Now that glibc is in place, we rebuild binutils targeting the new system's headers and libraries. This replaces the pass-1 cross-binutils with a proper version.
lfs$ cd $LFS/sources/binutils-2.46.0
lfs$ rm -rf build && mkdir build && cd build
lfs$ ../configure \
--prefix=/usr \
--build=$(../config.guess) \
--host=$LFS_TGT \
--disable-nls \
--enable-shared \
--enable-gprofng=no \
--disable-werror \
--enable-64-bit-bfd \
--enable-new-dtags \
--enable-default-hash-style=gnu
lfs$ make -j$(nproc)
lfs$ make DESTDIR=$LFS install
# Remove static libraries not needed at runtime
lfs$ rm -v $LFS/usr/lib/lib{bfd,ctf,ctf-nobfd,opcodes,sframe}.{a,la}
GCC — Pass 2
The full GCC, with C, C++, shared libraries, threading support, and all standard features enabled. This replaces the minimal pass-1 compiler.
lfs$ cd $LFS/sources/gcc-15.2.0
lfs$ rm -rf build && mkdir build && cd build
lfs$ ../configure \
--build=$(../config.guess) \
--host=$LFS_TGT \
--target=$LFS_TGT \
LDFLAGS_FOR_TARGET=-L$PWD/$LFS_TGT/libgcc \
--prefix=/usr \
--with-build-sysroot=$LFS \
--enable-default-pie \
--enable-default-ssp \
--disable-nls \
--disable-multilib \
--disable-libatomic \
--disable-libgomp \
--disable-libquadmath \
--disable-libsanitizer \
--disable-libssp \
--disable-libvtv \
--enable-languages=c,c++
lfs$ make -j$(nproc)
lfs$ make DESTDIR=$LFS install
# Create a versioned cc symlink
lfs$ ln -sv gcc $LFS/usr/bin/cc
$LFS. Everything from this point forward is compiled using tools that live inside the target system, not the host.
Entering the Chroot Environment
From here we switch from building for the target system to building inside it. The chroot gives us an isolated environment where the new system's tools are the only tools available.
Create the Full Directory Structure
root# mkdir -pv $LFS/{dev,proc,sys,run}
root# mkdir -pv $LFS/usr/{bin,lib,sbin}
root# mkdir -pv $LFS/{boot,home,mnt,opt,srv}
root# mkdir -pv $LFS/etc/{opt,sysconfig}
root# mkdir -pv $LFS/lib/firmware
root# mkdir -pv $LFS/media/{floppy,cdrom}
root# mkdir -pv $LFS/usr/{include,src}
root# mkdir -pv $LFS/usr/share/{color,dict,doc,info,locale,man}
root# mkdir -pv $LFS/usr/share/{misc,terminfo,zoneinfo}
root# mkdir -pv $LFS/usr/share/man/man{1..8}
root# mkdir -pv $LFS/var/{cache,local,log,mail,opt,spool}
root# mkdir -pv $LFS/var/lib/{color,misc,locate}
# Create FHS compatibility symlinks
root# for d in bin lib sbin; do
ln -sv usr/$d $LFS/$d
done
root# ln -sv usr/lib $LFS/lib64
root# install -dv -m 0750 $LFS/root
root# install -dv -m 1777 $LFS/tmp $LFS/var/tmp
Mount Virtual Filesystems
root# mount -v --bind /dev $LFS/dev
root# mount -vt devpts devpts -o gid=5,mode=0620 $LFS/dev/pts
root# mount -vt proc proc $LFS/proc
root# mount -vt sysfs sysfs $LFS/sys
root# mount -vt tmpfs tmpfs $LFS/run
root# if [ -h $LFS/dev/shm ]; then
install -v -d -m 1777 $LFS$(realpath /dev/shm)
else
mount -vt tmpfs -o nosuid,nodev tmpfs $LFS/dev/shm
fi
Enter the Chroot
root# chroot "$LFS" /usr/bin/env -i \
HOME=/root \
TERM="$TERM" \
PS1='(lfs chroot) \u:\w\$ ' \
PATH=/usr/bin:/usr/sbin \
MAKEFLAGS="-j$(nproc)" \
TESTSUITEFLAGS="-j$(nproc)" \
/bin/bash --login
The prompt changes to (lfs chroot). You are now inside your new system.
Core Package Builds
Inside the chroot, we now build all the essential packages that make up a usable system. Below is the required build order — dependencies flow downward.
1. man-pages
root# tar -xf man-pages-6.17.tar.xz
root# rm -v man-pages-6.17/man3/crypt*.3
root# make -C man-pages-6.17 prefix=/usr install
2. zlib
root# cd zlib-1.3.2 && ./configure --prefix=/usr
root# make -j$(nproc) && make install
root# rm -fv /usr/lib/libz.a
3. bzip2
root# cd bzip2-1.0.8
root# patch -Np1 -i ../bzip2-1.0.8-install_docs-1.patch
root# sed -i 's@\(ln -s -f \)$(PREFIX)/bin/@\1@' Makefile
root# sed -i "s@(PREFIX)/man@(PREFIX)/share/man@g" Makefile
root# make -f Makefile-libbz2_so
root# make clean && make && make PREFIX=/usr install
root# cp -av libbz2.so.* /usr/lib && ln -sv libbz2.so.1.0.8 /usr/lib/libbz2.so
4. xz
root# cd xz-5.8.2
root# ./configure --prefix=/usr --disable-static --docdir=/usr/share/doc/xz-5.8.2
root# make -j$(nproc) && make install
5. ncurses
root# cd ncurses-6.6
root# ./configure \
--prefix=/usr \
--mandir=/usr/share/man \
--with-shared \
--without-debug \
--without-normal \
--with-cxx-shared \
--enable-pc-files \
--with-pkg-config-libdir=/usr/lib/pkgconfig
root# make -j$(nproc) && make install
root# ln -sv libncursesw.so /usr/lib/libncurses.so
6. readline
root# cd readline-8.3
root# ./configure --prefix=/usr \
--disable-static \
--with-curses \
--docdir=/usr/share/doc/readline-8.3
root# make -j$(nproc) SHLIB_LIBS="-lncursesw"
root# make install SHLIB_LIBS="-lncursesw"
7. bash
root# cd bash-5.3
root# ./configure \
--prefix=/usr \
--without-bash-malloc \
--with-installed-readline \
--docdir=/usr/share/doc/bash-5.3
root# make -j$(nproc) && make install
root# ln -sv bash /usr/bin/sh
8. coreutils
root# cd coreutils-9.10
root# patch -Np1 -i ../coreutils-9.10-i18n-2.patch
root# ./configure \
--prefix=/usr \
--enable-no-install-program=kill,uptime \
--docdir=/usr/share/doc/coreutils-9.10
root# make -j$(nproc) && make install
root# mv -v /usr/bin/chroot /usr/sbin
System-Level Packages
The following packages form the system infrastructure layer. Build them in order.
util-linux
root# cd util-linux-2.41.3
root# ./configure \
ADJTIME_PATH=/var/lib/hwclock/adjtime \
--bindir=/usr/bin \
--libdir=/usr/lib \
--runstatedir=/run \
--sbindir=/usr/sbin \
--disable-chfn-chsh \
--disable-login \
--disable-nologin \
--disable-su \
--disable-setpriv \
--disable-runuser \
--disable-pylibmount \
--disable-liblastlog2 \
--disable-static \
--without-python \
--docdir=/usr/share/doc/util-linux-2.41.3
root# make -j$(nproc) && make install
e2fsprogs
root# cd e2fsprogs-1.47.3 && mkdir build && cd build
root# ../configure \
--prefix=/usr \
--sysconfdir=/etc \
--enable-elf-shlibs \
--disable-libblkid \
--disable-libuuid \
--disable-uuidd \
--disable-fsck
root# make -j$(nproc) && make install
root# rm -fv /usr/lib/{libcom_err,libe2p,libext2fs,libss}.a
shadow
root# cd shadow-4.19.3
root# sed -i 's/groups$(EXEEXT) //' src/Makefile.in
root# find man -name Makefile.in -exec sed -i 's/groups\.1 //' {} \;
root# ./configure \
--sysconfdir=/etc \
--disable-static \
--with-{b,y}crypt \
--with-group-name-max-length=32
root# make -j$(nproc) && make exec_prefix=/usr install
root# pwconv && grpconv
root# passwd root
procps-ng
root# cd procps-ng-4.0.6
root# ./configure \
--prefix=/usr \
--docdir=/usr/share/doc/procps-ng-4.0.6 \
--disable-static \
--disable-kill \
--enable-watch8bit \
--with-systemd
root# make -j$(nproc) && make install
Remaining Utilities
Build the following packages using the standard pattern ./configure --prefix=/usr && make -j$(nproc) && make install. Refer to each project's own README for flags specific to your use case:
- grep 3.12 —
--prefix=/usr - gawk 5.3.2 —
--prefix=/usr - sed 4.9 —
--prefix=/usr - tar 1.35 —
--prefix=/usr - gzip 1.14 —
--prefix=/usr - make 4.4.1 —
--prefix=/usr - patch 2.8 —
--prefix=/usr - findutils 4.10.0 —
--prefix=/usr --localstatedir=/var/lib/locate - diffutils 3.12 —
--prefix=/usr - file 5.46 —
--prefix=/usr
Building the Linux Kernel 7.1.5
The kernel is the final major compile. We use make defconfig as a starting point and then enable a minimal set of options required for a bootable system.
root# cd /sources/linux-7.1.5
root# make mrproper
# Start with defconfig for your architecture
root# make defconfig
# Open the interactive config menu (optional, recommended for learning)
root# make menuconfig
Essential Kernel Options
Use menuconfig to verify or enable these options. The CONFIG_ names are searchable with /:
| Config Symbol | Setting | Reason |
|---|---|---|
| CONFIG_EXPERT | y | Unlocks advanced options |
| CONFIG_SYSFS | y | Required for udev/systemd |
| CONFIG_PROC_FS | y | Required for /proc |
| CONFIG_TMPFS | y | Required for /tmp, /run |
| CONFIG_UNIX | y | Unix domain sockets (systemd) |
| CONFIG_INOTIFY_USER | y | systemd file watching |
| CONFIG_EXT4_FS | y | Root filesystem |
| CONFIG_CGROUPS | y | systemd cgroup management |
| CONFIG_NAMESPACES | y | systemd namespace isolation |
| CONFIG_NET | y | Networking |
| CONFIG_INET | y | TCP/IP stack |
| CONFIG_BLK_DEV_SD | y | SCSI/SATA disk support |
| CONFIG_ATA | y | ATA controllers |
| CONFIG_EFI_STUB | y | UEFI boot support |
# Build the kernel, modules, and install
root# make -j$(nproc)
root# make modules_install
# Install the kernel image and System.map
root# cp -iv arch/x86/boot/bzImage /boot/vmlinuz-7.1.5-forge
root# cp -iv System.map /boot/System.map-7.1.5
root# cp -iv .config /boot/config-7.1.5
=y) rather than as modules (=m). This simplifies the initramfs situation considerably.
System Configuration Files
/etc/fstab
root# cat > /etc/fstab << "EOF"
# <device> <mountpoint> <type> <options> <dump> <pass>
/dev/sdb3 / ext4 defaults 1 1
/dev/sdb2 /boot ext4 defaults 0 2
/dev/sdb1 /boot/efi vfat umask=0077 0 1
tmpfs /tmp tmpfs defaults,nosuid,nodev 0 0
EOF
/dev/sdbX with UUIDs from blkid to ensure correct mounting regardless of device enumeration order. Example: UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Hostname
root# echo "forge" > /etc/hostname
Hosts File
root# cat > /etc/hosts << "EOF"
127.0.0.1 localhost
127.0.1.1 forge
::1 localhost ip6-localhost ip6-loopback
EOF
Timezone
root# ln -sfv /usr/share/zoneinfo/UTC /etc/localtime
# Or for a specific timezone:
root# ln -sfv /usr/share/zoneinfo/Asia/Kolkata /etc/localtime
Locale
root# localedef -i en_US -f UTF-8 en_US.UTF-8
root# cat > /etc/locale.conf << "EOF"
LANG=en_US.UTF-8
EOF
Input Key Bindings
root# cat > /etc/inputrc << "EOF"
set horizontal-scroll-mode Off
set meta-flag On
set input-meta On
set convert-meta Off
set output-meta On
set bell-style none
"\eOd": backward-word
"\eOc": forward-word
"\e[1~": beginning-of-line
"\e[4~": end-of-line
"\e[5~": beginning-of-history
"\e[6~": end-of-history
"\e[3~": delete-char
"\e[2~": quoted-insert
EOF
Shells
root# cat > /etc/shells << "EOF"
/bin/sh
/bin/bash
EOF
Placeholder OS identity
Minimal stub so the system is not anonymous before Chapter 20. Replace with the full Forge kit later.
root# cat > /etc/os-release << "EOF"
NAME="Forge Linux"
PRETTY_NAME="Forge Linux 0.1.0 (anvil)"
ID=forge
VERSION_ID="0.1.0"
VERSION_CODENAME=anvil
HOME_URL="https://forge.example/"
EOF
root# ln -sfv /etc/os-release /usr/lib/os-release
systemd 259.1
systemd replaces SysVinit and provides the init system, service manager, journal, udev, network management, and much more. It has a significant number of dependencies — build them first.
Dependencies to build first
- libcap 2.77 — POSIX capabilities library (configure with
--prefix=/usr --disable-static) - libxcrypt 4.5.2 — Modern crypt() implementation for password hashing
- cryptsetup 2.8.0 — Disk encryption (optional but recommended)
- kmod 34.2 — Kernel module loading (
modprobe,lsmod) - libunistring 1.3 — Unicode string library
- libffi 3.5.2 — Foreign function interface
- Python 3.14.3 — Required by systemd's test suite and tools
Build systemd with Meson
root# cd /sources/systemd-259.1
root# pip3 install jinja2 pyelftools
root# meson setup --prefix=/usr \
--buildtype=release \
-Ddefault-dnssec=no \
-Dfirstboot=false \
-Dinstall-tests=false \
-Dldconfig=false \
-Dman=disabled \
-Dsysusers=false \
-Drpmmacrosdir=no \
-Dhomed=disabled \
-Duserdb=false \
-Dmode=release \
-Dpamconfdir=no \
-Ddev-kvm-mode=0660 \
-Dnobody-group=nogroup \
-Dsysupdate=disabled \
-Dukify=disabled \
build
root# ninja -C build && ninja -C build install
Post-install Configuration
# Disable graphical login (not needed for minimal system)
root# systemctl set-default multi-user.target
# Create machine-id (required by systemd-journald)
root# systemd-machine-id-setup
# Enable basic services
root# systemctl enable systemd-networkd
root# systemctl enable systemd-resolved
Network Configuration via systemd-networkd
root# cat > /etc/systemd/network/10-eth0.network << "EOF"
[Match]
Name=eth0
[Network]
DHCP=yes
DNS=1.1.1.1
DNS=8.8.8.8
EOF
GRUB 2.14 — The Bootloader
GRUB (Grand Unified Bootloader) is what the firmware hands control to after POST. It then loads the kernel and passes it an initial set of parameters.
Build GRUB
root# cd /sources/grub-2.14
root# ./configure \
--prefix=/usr \
--sysconfdir=/etc \
--disable-efiemu \
--enable-grub-mkfont \
--with-platform=efi \
--target=x86_64 \
--disable-werror
root# make -j$(nproc) && make install
Install GRUB to the ESP
# UEFI install (recommended for all modern systems)
root# grub-install \
--target=x86_64-efi \
--efi-directory=/boot/efi \
--bootloader-id=LFS \
--recheck
# Legacy BIOS install (if your VM is not UEFI)
root# grub-install --target=i386-pc /dev/sdb
Generate grub.cfg
root# cat > /boot/grub/grub.cfg << "EOF"
# GRUB 2 configuration for Forge Linux
set default=0
set timeout=5
insmod part_gpt
insmod ext2
insmod fat
# Load EFI video support
if [ x$grub_platform = xxefi ]; then
insmod linuxefi
fi
menuentry "Forge Linux 0.1 — Linux 7.1.5" {
linux /boot/vmlinuz-7.1.5-forge root=/dev/sdb3 rw quiet
# For UEFI: linuxefi /boot/vmlinuz-7.1.5-forge root=/dev/sdb3 rw quiet
}
EOF
root=/dev/sdb3 with root=UUID=$(blkid -s UUID -o value /dev/sdb3) to make the config portable across disk re-enumeration.
First Boot
Before rebooting, exit the chroot cleanly and unmount all virtual filesystems.
Exit and Unmount
# Inside the chroot
root# logout
# Back on the Fedora host
root# umount -v $LFS/dev/pts
root# umount -v $LFS/dev
root# umount -v $LFS/run
root# umount -v $LFS/proc
root# umount -v $LFS/sys
root# umount -v $LFS/boot/efi
root# umount -v $LFS/boot
root# umount -v $LFS
Boot the New System
- In VirtualBox/GNOME Boxes, change the VM boot order to boot from /dev/sdb first
- Reboot the virtual machine
- GRUB should appear with the "Forge Linux 0.1 — Linux 7.1.5" menu entry
- Select it and watch the kernel boot messages
- systemd should hand off to a login prompt on
tty1 - Log in as
rootwith the password you set in Chapter 14
Verify the System
root# uname -a
Linux forge 7.1.5 #1 SMP ... x86_64 GNU/Linux
root# cat /etc/os-release
NAME="Forge Linux"
PRETTY_NAME="Forge Linux 0.1.0 (anvil)"
ID=forge
VERSION_ID="0.1.0"
root# systemctl status
● forge
State: running
Units: 168 loaded (...)
Jobs: 0 queued
Failed: 0 units
root# ip link
root# ping -c3 1.1.1.1
What Next?
Your system boots. Now make it yours — continue with Chapters 20–28:
- Brand it — identity kit, os-release, MOTD, GRUB theme
- Package manager — ports tree or pacman/rpm path
- Ship media — live ISO, qcow2, installer
- Release process — signing, SBOM, versioning
- Userspace — SSH, desktop via BLFS
Brand Identity — Own Your Distro
A bootable rootfs is not yet a distribution. A distribution has a name people say out loud, a legal identity, consistent strings in every UI surface, and a versioning story. This chapter defines Forge Linux as the default brand for the rest of the guide. Replace the kit once; reuse it everywhere.
Identity fields
| Field | Value |
|---|---|
| NAME | Forge Linux |
| ID | forge |
| VERSION_ID | 0.1.0 |
| VERSION_CODENAME | anvil |
| PRETTY_NAME | Forge Linux 0.1.0 (anvil) |
| HOME_URL | https://forge.example/ |
| BUG_REPORT_URL | https://forge.example/bugs |
| SUPPORT_URL | https://forge.example/docs |
| VENDOR | Your Name / Org |
| ANSI_COLOR | 1;32 |
Naming rules
- ID — lowercase, no spaces (
forge) - NAME — human-facing product name
- Codenames — optional per release (anvil → bellows → crucible…)
- Avoid trademarks — do not embed Ubuntu, Red Hat, Fedora, Debian in ID/NAME
- Legal — branding does not relicense GPL components; keep COPYING/NOTICE
Single source of truth — identity kit
Create this file on the host and copy it into the target. Every branding script sources it.
root# mkdir -pv /opt/forge-brand
root# cat > /opt/forge-brand/identity.env << 'EOF'
# === Forge Linux identity kit — edit once ===
export DISTRO_NAME="Forge Linux"
export DISTRO_NAME_SHORT="Forge"
export DISTRO_ID="forge"
export DISTRO_VERSION="0.1.0"
export DISTRO_CODENAME="anvil"
export DISTRO_PRETTY="${DISTRO_NAME} ${DISTRO_VERSION} (${DISTRO_CODENAME})"
export DISTRO_HOME_URL="https://forge.example/"
export DISTRO_BUG_URL="https://forge.example/bugs"
export DISTRO_SUPPORT_URL="https://forge.example/docs"
export DISTRO_VENDOR="Your Name or Organization"
export DISTRO_ANSI_COLOR="1;32"
export DISTRO_LOGO="forge-logo"
export DISTRO_KERNEL_LOCALVERSION="-forge"
export DISTRO_HOSTNAME="forge"
export DISTRO_GRUB_ID="Forge"
export DISTRO_ISO_LABEL="FORGE_0_1"
export DISTRO_CONTACT="maintainers@forge.example"
export DISTRO_COLOR_PRIMARY="#4ade80"
export DISTRO_COLOR_BG="#0d0f12"
export DISTRO_TAGLINE="Built from source. Owned by you."
EOF
root# source /opt/forge-brand/identity.env
root# mkdir -pv $LFS/usr/share/forge
root# cp -v /opt/forge-brand/identity.env $LFS/usr/share/forge/
Brand asset checklist
- Wordmark — SVG + PNG @1x/@2x, light and dark variants
- Mark / icon — 512×512 and 1024×1024; also 48/64/128 for menus
- Favicon — 32×32 and 16×16 for docs site
- GRUB background — 1920×1080 PNG, dark, high-contrast menu text
- Plymouth theme — logo + spinner (when graphical boot exists)
- Wallpapers — 1–3 desktop images for a future DE spin
- ASCII / neofetch logo — monochrome terminal art
- Installer art — sidebar banner for TUI/GUI installer
On-target brand directory layout
/usr/share/forge/
identity.env
branding/
logo.svg
logo-dark.svg
mark.png
grub-bg.png
plymouth/
wallpapers/
NOTICE # third-party license rollup
/usr/share/pixmaps/forge-logo.png
/usr/share/backgrounds/forge/
/etc/os-release
/etc/forge-release
/usr/share/forge/NOTICE listing Linux, glibc, GCC runtime, systemd, GRUB, and major GNU tools.
OS Identity Files
These files answer “what OS is this?” for hostnamectl, containers, installers, and humans. Apply inside the chroot after sourcing identity.env.
/etc/os-release (required)
root# source /usr/share/forge/identity.env
root# cat > /etc/os-release << EOF
NAME="${DISTRO_NAME}"
PRETTY_NAME="${DISTRO_PRETTY}"
ID=${DISTRO_ID}
ID_LIKE=lfs
VERSION="${DISTRO_VERSION} (${DISTRO_CODENAME})"
VERSION_ID="${DISTRO_VERSION}"
VERSION_CODENAME=${DISTRO_CODENAME}
BUILD_ID=$(date -u +%Y%m%d)
HOME_URL="${DISTRO_HOME_URL}"
DOCUMENTATION_URL="${DISTRO_SUPPORT_URL}"
SUPPORT_URL="${DISTRO_SUPPORT_URL}"
BUG_REPORT_URL="${DISTRO_BUG_URL}"
PRIVACY_POLICY_URL="${DISTRO_HOME_URL}privacy"
LOGO=${DISTRO_LOGO}
ANSI_COLOR="${DISTRO_ANSI_COLOR}"
VENDOR_NAME="${DISTRO_VENDOR}"
EOF
root# ln -sfv /etc/os-release /usr/lib/os-release
/etc/lsb-release (compatibility)
root# cat > /etc/lsb-release << EOF
DISTRIB_ID=${DISTRO_NAME_SHORT}
DISTRIB_RELEASE=${DISTRO_VERSION}
DISTRIB_CODENAME=${DISTRO_CODENAME}
DISTRIB_DESCRIPTION="${DISTRO_PRETTY}"
EOF
/etc/forge-release
root# cat > /etc/forge-release << EOF
${DISTRO_PRETTY}
Built: $(date -u +%Y-%m-%dT%H:%MZ)
Kernel target: 7.1.5
Toolchain: GCC 15.2.0 / glibc 2.43 / Binutils 2.46.0
EOF
Login banners — /etc/issue & MOTD
root# cat > /etc/issue << EOF
${DISTRO_NAME} ${DISTRO_VERSION} (\\n) (\\l)
${DISTRO_TAGLINE}
EOF
root# cp -v /etc/issue /etc/issue.net
root# cat > /etc/motd << EOF
███████╗ ██████╗ ██████╗ ██████╗ ███████╗
██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝
█████╗ ██║ ██║██████╔╝██║ ███╗█████╗
██╔══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝
██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗
╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
${DISTRO_PRETTY}
${DISTRO_TAGLINE}
Docs: ${DISTRO_SUPPORT_URL}
EOF
root# cat > /etc/profile.d/forge-motd.sh << 'EOF'
if [ -n "$PS1" ] && [ -z "$FORGE_MOTD_SHOWN" ]; then
export FORGE_MOTD_SHOWN=1
echo "Kernel: $(uname -r) | $(uptime -p 2>/dev/null || true)"
fi
EOF
Hostname & kernel localversion
root# echo "$DISTRO_HOSTNAME" > /etc/hostname
root# hostnamectl set-hostname "$DISTRO_HOSTNAME"
root# hostnamectl set-hostname --pretty "$DISTRO_PRETTY"
# When building the kernel (Ch. 15):
root# cd /sources/linux-7.1.5
root# scripts/config --set-str LOCALVERSION "-forge"
root# make -j$(nproc) && make modules_install
root# cp -iv arch/x86/boot/bzImage /boot/vmlinuz-7.1.5-forge
# uname -r → 7.1.5-forge
org.opencontainers.image.title, .version, .vendor, .source.
Visual Branding
Make boot and login intentional: GRUB theme, console, optional Plymouth, terminal logos.
GRUB theme + branded menu
root# source /usr/share/forge/identity.env
root# mkdir -pv /boot/grub/themes/forge
root# cp /usr/share/forge/branding/grub-bg.png /boot/grub/themes/forge/background.png
root# cat > /boot/grub/themes/forge/theme.txt << 'EOF'
desktop-image: "background.png"
title-text: "Forge Linux"
title-color: "#4ade80"
message-color: "#e8edf5"
+ boot_menu {
left = 15%; width = 70%; top = 30%; height = 40%
item_color = "#c8d0e0"
selected_item_color = "#0d0f12"
item_height = 28; item_padding = 8; item_spacing = 6
}
EOF
root# ROOT_UUID=$(blkid -s UUID -o value /dev/sdb3)
root# cat > /boot/grub/grub.cfg << EOF
set default=0
set timeout=5
set gfxmode=auto
insmod all_video
insmod gfxterm
insmod png
insmod part_gpt
insmod ext2
terminal_output gfxterm
set theme=/boot/grub/themes/forge/theme.txt
export theme
menuentry "${DISTRO_NAME} ${DISTRO_VERSION} — Linux 7.1.5" {
linux /boot/vmlinuz-7.1.5-forge root=UUID=${ROOT_UUID} rw quiet
}
menuentry "${DISTRO_NAME} (recovery)" {
linux /boot/vmlinuz-7.1.5-forge root=UUID=${ROOT_UUID} rw single
}
EOF
Plymouth (graphical splash)
Build Plymouth via BLFS when DRM/KMS + initramfs exist. Theme skeleton:
/usr/share/plymouth/themes/forge/forge.plymouth
[Plymouth Theme]
Name=Forge
Description=Forge Linux boot splash
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/forge
ScriptFile=/usr/share/plymouth/themes/forge/forge.script
root# plymouth-set-default-theme -R forge
# kernel cmdline: quiet splash — rebuild initramfs after install
fastfetch / neofetch ASCII
root# cat > /usr/share/forge/ascii.txt << 'EOF'
#####
## ## Forge Linux
## Built from source
## ###
## ##
#####
EOF
# fastfetch -l /usr/share/forge/ascii.txt
forge-apply-brand helper
root# cat > /usr/sbin/forge-apply-brand << 'EOF'
#!/bin/bash
set -euo pipefail
. /usr/share/forge/identity.env
# regenerate os-release, issue, motd, hostname from identity.env (Ch. 21)
echo "$DISTRO_HOSTNAME" > /etc/hostname
echo "Applied ${DISTRO_PRETTY}"
EOF
root# chmod 755 /usr/sbin/forge-apply-brand
Package Manager — From Tarballs to a Distro
Without packages you have a golden image you cannot update cleanly. Choose early:
A. Scripted ports tree
Per-package build scripts + manifest. Best learning path; start here for Forge 0.1.
B. Binary packages
Adopt pacman, dpkg/apt, or rpm/dnf. Real distro UX; more work.
C. Image-based
OSTree / A/B images. Excellent for appliances; different model than classic LFS.
D. Hybrid
Immutable base + Flatpak/Nix for apps. Pragmatic for desktops.
Forge ports tree layout
/var/lib/forge/ports/
toolchain/gcc/Pkgfile
base/bash/Pkgfile
base/coreutils/Pkgfile
net/openssh/Pkgfile
meta/base-system/Pkgfile
/var/cache/forge/sources/
/var/cache/forge/packages/
Minimal Pkgfile
name=hello
version=2.12.2
release=1
source=(https://ftp.gnu.org/gnu/hello/hello-${version}.tar.gz)
build() {
cd "${name}-${version}"
./configure --prefix=/usr
make -j$(nproc)
}
package() {
make DESTDIR="$pkgdir" install
}
forgepkg sketch
root# cat > /usr/sbin/forgepkg << 'EOF'
#!/bin/bash
# Educational package helper — build | install | list
set -euo pipefail
PORTS=${PORTS:-/var/lib/forge/ports}
PKGDB=/var/lib/forge/db
CACHEDIR=/var/cache/forge/packages
mkdir -p "$PKGDB" "$CACHEDIR"
cmd=${1:-}; shift || true
case "$cmd" in
build) echo "Build port $1 (download, build(), package to $CACHEDIR)";;
install) echo "Install package file $1; record in $PKGDB";;
list) cat "$PKGDB/installed" 2>/dev/null || true;;
*) echo "Usage: forgepkg {build|install|list}"; exit 1;;
esac
EOF
root# chmod 755 /usr/sbin/forgepkg
Signed repository layout
https://repo.forge.example/
forge/os/x86_64/
forge-core.db
forge-core.db.sig
bash-5.3-1-x86_64.pkg.tar.zst
bash-5.3-1-x86_64.pkg.tar.zst.sig
iso/
forge-0.1.0-anvil-x86_64.iso
SHA256SUMS
SHA256SUMS.asc
If you adopt pacman
- Build deps (openssl, curl, libarchive, gpgme) then pacman
- Configure
/etc/pacman.confwith[forge-core]/[forge-extra] repo-add forge-core.db.tar.gz *.pkg.tar.zst- Host over HTTPS; sign packages with distro GPG key (Ch. 27)
- Document
pacman -Syuas the user upgrade path
Userspace Stack — Make It Usable
After first boot you have a console OS. Prioritize by persona.
Tier 0 — Always (server / appliance base)
| Package | Why |
|---|---|
| iproute2 | ip, ss — real network control |
| OpenSSH | Remote admin |
| curl / wget | Fetch sources and updates |
| ca-certificates | TLS trust store |
| vim or nano | Edit configs |
| sudo | Least-privilege admin user |
| chrony / timesyncd | Correct clock |
| logrotate | Log hygiene |
Tier 1 — Developer box
git, strace, gdb, lsof, htop/btop, python3, tmux, man-db.
Tier 2 — Desktop (optional, BLFS)
- DRM/Mesa + libinput + Xorg or Wayland (Sway/Weston)
- Fonts + fontconfig
- PipeWire for audio
- XFCE (lighter) or GNOME/KDE
- Display manager: greetd, lightdm, or gdm
- Forge wallpapers + theme tokens from brand colors
Admin user
root# useradd -m -s /bin/bash -c "Forge Admin" forgeuser
root# passwd forgeuser
root# usermod -aG wheel forgeuser
nftables baseline
root# nft add table inet filter
root# nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
root# nft add rule inet filter input ct state established,related accept
root# nft add rule inet filter input iif lo accept
root# nft add rule inet filter input tcp dport 22 accept
Live ISO & Disk Images
Users install from images. Plan to ship a live ISO, a raw/qcow2 disk image, and optionally an OCI base image from the same rootfs.
Host tooling (Fedora)
root# dnf install xorriso mtools squashfs-tools dosfstools \
grub2-tools grub2-tools-extra grub2-efi-x64-modules \
syslinux isomd5sum qemu-img edk2-ovmf
Pipeline
- Export clean rootfs (exclude /sources, /tools, caches)
- Build initramfs with live/overlay hooks
mksquashfsrootfs →LiveOS/squashfs.img- Stage ISO tree: kernel, initrd, GRUB EFI + BIOS, brand assets
- Hybrid ISO via xorriso
- SHA256 + GPG sign
Export rootfs
root# export LFS=/mnt/lfs
root# tar -C $LFS \
--exclude=./sources --exclude=./tools \
-cJf /var/tmp/forge-rootfs.tar.xz .
ISO skeleton
/var/tmp/forge-iso/
EFI/BOOT/BOOTX64.EFI
boot/grub/grub.cfg
boot/grub/themes/forge/
images/pxeboot/vmlinuz
images/pxeboot/initrd.img
LiveOS/squashfs.img
LICENSE
README.txt
xorriso (conceptual)
root# source /opt/forge-brand/identity.env
root# xorriso -as mkisofs \
-iso-level 3 \
-full-iso9660-filenames \
-volid "$DISTRO_ISO_LABEL" \
-appid "$DISTRO_NAME" \
-publisher "$DISTRO_VENDOR" \
-o /var/tmp/${DISTRO_ID}-${DISTRO_VERSION}-${DISTRO_CODENAME}-x86_64.iso \
/var/tmp/forge-iso/
# Add eltorito BIOS + EFI alternate boot images per your GRUB embed setup
# Test: qemu-system-x86_64 -m 2048 -cdrom … and UEFI with OVMF
VM / cloud image
root# qemu-img create -f raw forge-0.1.0.img 8G
root# losetup -fP forge-0.1.0.img
# partition, mkfs, rsync rootfs, grub-install, forge-apply-brand
# qemu-img convert -c -O qcow2 forge-0.1.0.img forge-0.1.0.qcow2
Checksums
root# cd /var/tmp
root# sha256sum forge-*.iso forge-*.qcow2 > SHA256SUMS
root# gpg --local-user release@forge.example \
--detach-sign --armor SHA256SUMS
Installer
An installer turns a rootfs into a product people can deploy without reading build chapters.
1. Shell installer
forge-install TUI: disk → partition → unpack → GRUB → password. Perfect for 0.1.
2. Calamares
Qt GUI + Forge branding pack — fastest polished installer path.
3. Custom GUI
Anaconda/Subiquity-class effort. Only if installer UX is the product.
4. Image flash
No installer — dd a preseeded image (appliances, SBCs, cloud).
Installer flow
- List block devices; destructive confirm
- GPT: ESP 512M, boot 1G, root rest (match Ch. 03)
- Format + mount at
/mnt/target - Unpack squashfs/rootfs
- chroot:
forge-apply-brand, root password, locale, timezone - grub-install + UUID-based grub.cfg
- Unmount; reboot
root# cat > /usr/sbin/forge-install << 'EOF'
#!/bin/bash
set -euo pipefail
. /usr/share/forge/identity.env 2>/dev/null || true
echo "=== ${DISTRO_NAME:-Forge Linux} installer ==="
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
read -r -p "Target disk (e.g. /dev/vda): " DISK
read -r -p "ERASE $DISK and install? type YES: " CONFIRM
[[ "$CONFIRM" == "YES" ]] || exit 1
# partition (sfdisk/parted), mkfs, rsync rootfs, grub, passwords …
echo "Install complete. Reboot into ${DISTRO_PRETTY:-Forge}."
EOF
root# chmod 755 /usr/sbin/forge-install
Calamares branding paths
/etc/calamares/branding/forge/
branding.desc
stylesheet.qss
logo.png
welcome.png
First-boot unit (cloud images)
root# cat > /etc/systemd/system/forge-firstboot.service << 'EOF'
[Unit]
Description=Forge Linux first boot
ConditionPathExists=!/var/lib/forge/firstboot-done
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/libexec/forge-firstboot
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
root# systemctl enable forge-firstboot.service
First-boot expands disks, regenerates machine-id, injects SSH keys (cloud-init), sets hostname from metadata.
Release Engineering
Shipping repeatedly is what makes a distro real.
Versioning
| Scheme | Example | When |
|---|---|---|
| SemVer + codename | 0.1.0 (anvil) | Forge default |
| CalVer | 2026.08 | Monthly snapshots / rolling |
| Point release | 1.0 → 1.1 | Stable series with backports |
Release checklist
- Freeze ports tree; git tag
v0.1.0 - Rebuild packages/images; pin SHA256 sources
- QEMU smoke: BIOS + UEFI boot, login, network, package install
- Publish ISO + qcow2 + SBOM; SHA256SUMS + GPG
- Release notes: toolchain, kernel, known issues, upgrade path
- Announce with verification instructions
Distro signing key
root# gpg --full-generate-key
# Identity: Forge Linux Release <release@forge.example>
root# gpg --export --armor release@forge.example \
> forge-release.pubkey.asc
# Publish on HTTPS + fingerprint in every ISO README
Release notes template
# Forge Linux 0.1.0 (anvil) — 2026-08-03
## Highlights
- First public release
- Linux 7.1.5, GCC 15.2, glibc 2.43, systemd 259.1
- Branded live ISO + raw image
## Known issues
- No GUI in base ISO
- forgepkg is educational — prefer ports scripts
## Verify
sha256sum -c SHA256SUMS
gpg --verify SHA256SUMS.asc
## Upgrade
Clean install recommended for 0.x
CI outline
# on tag v*:
# build-rootfs → build-iso → qemu-smoke → sign → publish
SBOM / auditability
- Pin tarball versions + hashes in ports tree
- Emit CycloneDX or SPDX for each release
- Set SOURCE_DATE_EPOCH where tooling allows
- Keep
sources.locknext to the git tag
Distro Roadmap — Beyond the Guide
| Release | Codename | Goals |
|---|---|---|
| 0.1 | anvil | Branded bootable system, SSH, docs, ISO alpha |
| 0.2 | bellows | Real package manager + signed repo + installer beta |
| 0.3 | crucible | Desktop spin and/or server hardening profile |
| 1.0 | hammer | Stable promise, LTS kernel option, security policy |
Product decisions
- Audience — learners, homelab, appliances, secure workstations?
- Update model — rolling, point releases, or image A/B?
- Architectures — x86_64 first; aarch64 when CI exists
- Governance — solo vs community; CoC; how packages enter the set
Docs site (brand surface)
/— product story + downloads/docs/install— verify ISO + install/docs/build— this guide/security— vuln report + PGP/brand— logo kit (SVG/PNG)
Master checklist — “I have a distro”
- Boots without the build host
/etc/os-releaseandhostnamectlshow my name- GRUB + login banners match brand
- Users can install/flash without reading toolchain chapters
- Documented update or rebuild path
- Artifacts checksummed and signed
- Licenses + NOTICE complete
- Rebuildable from a git tag
Companions: LFS · BLFS · os-release(5) · kernel.org