This is inspired by MyLFS, I forked it to my github and built a fully working x windows stack.
The shell script version is not future proof and tedious to port to new versions.
by default mylfs looks for recipes logs and mnt in the same directory that it was launched. These can be configured.
The original plan was to write this in zig. However, after playing around with it for almost a week I can not get the yaml files to parse with any of the native zig yaml libraries. -- After my hike, the landscape has seen good development. I would like to return to this.
NOT all config options are implemented yet! Patches are welcome if anyone wants to implement a feature not there yet!
If a package fails to download and it's from the linux kernel git site. I had to manually download them first in a browser and then wget worked. -- It's a bot protection mechanism they're using. -- If you download the right version and drop it in place the program will just pick up like normal. As if it was already downloaded.
https://git.kernel.org https://youtu.be/3MTyv7hystI?si=jcm_VvVByv3tWD6Q
On the other hand, some packages connect to the internet to download things. If they fail most of the time, restarting them works.
PS. My spelling is bad... I'm aware. To the best of my ablity I used Google doc to spell check this readme. However, through out you'll probably find a lot of miss spelled stuff.
This is a set of custom recipes to build LFS with Musl+Clang (Secondary compiler) + dinit
Musl documention is a bit lacking. However this page points to two different standards: https://musl.libc.org/doc/1.1.24/manual.txt
I have Downloaded them and copy them over to /usr/share/doc/musl/
One is a pdf the other is html. Both a copy converted to plain text.
Patches related to musl build came from https://github.com/richfelker/musl-cross-make
This will build the base system with a handful of extra packages in phase 5 (including efi support).
sudo ./mylfsThis is a hack. However, in phase5 it's build function has logic to use --start-package List[str] and pulls out it builds deps
./mylfs --start-phase 5 --start-package nwipe
mylfs-py edition
========================================
Warn: skipping tests
Passed: Required tools found
Initializing recipes from /data/code/py/mylfs-py/dkrecipes
Info: Copying DB from previous build!
Passed: Preserved DB from previous build!
Passed: Loaded 631 recipes.
Passed: Created user and group lfs
Passed: Created lfs user and group
Failed: could not chown: /mnt/lfs/ lfs user and group
Phase 5 - Building final system
========================================
[SKIP] make already built
[SKIP] glibc already built
[SKIP] pkgconfig already built
[SKIP] ncurses already built
[SKIP] libeconf already built
[SKIP] libconfig already built
[SKIP] libpcap already built
[SKIP] parted already built
[SKIP] libgudev already built
[SKIP] libblockdev already built
[SKIP] nwipe already built
Passed: unmounted tmp fsIf a dep failed to build or there isn't a recipe for it, it will skip it missing/failed deps -> djok
There is a know bug, where if you pass in a package to --start-package that doesn't exist: djok, mylfs-py will just build all packages. However, if you pass it in with another valid package say ./mylfs --start-phase 5 --start-package nwipe djok nwipe. It will build nwipe and all of its deps. However in either case it never tells your that recipe djok doesn't exist.
or edit config.yml and change recipes_path: "recipes/" to recipes_path: "dkrecipes/"
If you changed to dkrecipes this will build batteries included distro. With anything I or my uncle might want to use or may want to explore. ~ This is a huge build.
If a build crashes you can restart that phase with the below command. This is often handy in phase 5 when you add packages to your recipes directory.
sudo ./mylfs --start-phase <number>While it's building you may want to see what is going on inside.
In the example below I was running mylfs on virtial terminal 3. The below command will print out all processes running on that terminal. -- You will need to change 3 with the virtual terminal you're running on or TTY.
virtual terminals ls /dev/pts*
on solus /dev/pts/*
you can grep each one from ps aux until your find the one you want
ps aux | grep pts/3If this doesn't work, you either need to manually unmount each instance of our bind mounts (dev, dev/{pts,shm}, proc, sys, run). Or you can just reboot. (type findmnt and you'll see why)
If you want to create an image file to load your distro in instead of installing to real hardware start here. Otherwise skip to Installation.
First we must create an image file. (I'm doing 40G as my dkrecipes currently is ~35G as a complete build)
status=progress is optionally.
dd if=/dev/zero of=lfs.img bs=1M count=40960 status=progressor if your filesystem supports it
truncate -s 40G lfs.imgNow we need to mount the img file.
sudo losetup -fP lfs.img
losetup -alosetup -a should give you an output like: /dev/loop0: []: (/home/dakota/lfs.img)
format your drive. I'm using cfdisk. However, you can use fdisk or any other tool. For simplicity the drive should be MBR (dos) with one ext[2-4] partition.
sudo cfdisk /dev/loop0Format the file system
sudo mkfs.ext4 /dev/loop0p1Create a place to mount our image
sudo mkdir -vp /mnt/lfs-image
sudo mount /dev/loop0p1 /mnt/lfs-imageOnce done Continue on with Installation as if it was on a real drive.
If you did not build in place. Either by mounting your drive to mnt/lfs or changing the relative build path to where your new drive is mounted you will need to transfer your files over. In this example we mounted our newroot partition to /mnt/usb, change it to wherever you mounted your new root fs.
As this command is set up it needs to be executed in the root of the mylfs-py directory.
sudo rsync -aAXHv --delete --info=progress2 \
--exclude=/dev/* \
--exclude=/proc/* \
--exclude=/sys/* \
--exclude=/run/* \
mnt/lfs/ /mnt/usb/Then you need to bind mount {/dev/{pts,shm},sys, run, proc} inside your mount point.
mount --bind /dev /mnt/usb/dev
mount --bind /proc /mnt/usb/proc
mount --bind /sys /mnt/usb/sys
mount --bind /run /mnt/usb/runif /dev/pts exist it should be mounted as well.
mount --bind /dev/pts /mnt/usb/dev/ptsOnce done chroot inside.
chroot /mnt/usb /usr/bin/bash --loginWhen done make sure to unmount your bind mounts
fstab & hosts need to be configured inside. This is a good time to make any last minute configuration to other files as well.
brave souls can try genfstab, however, I can't guarantee that it will work. Otherwise refer to the LFS book for setting up this file. This modified version also allows for a -C to be used with -P to allow for a clean style LFS fstab setup. --- Experimental
genfstab -LP > /etc/fstabMake sure to set a root password.
NOTE: This should be patched. However, when I first booted I could not set the root password. Doing this seems to have fixed that issue.
In our Chroot we need to install grub2 to our drive. My drive is /dev/sdc make sure you properly identify which drive you are actually using.
If you want to install legacy mode for qemu or older hardware and your system supports UEFI you need to pass --target=i386-pc
With a gpt formatted disk, to install legacy mode you need a 1-2 mb unformatted space at the beginning of the disk that you mark as bios-grub. This will also allow you to make a medium that can boot both in legacy and UEFI as long as the kernel supports it. To do this after you install grub to the root of the drive you would then install grub normally for an efi setup with a fat32 (vfat) fs as your esp/boot/efi depending on the setup that is placed right after the 1-2 mb unformatted space for legacy grub. -- Make sure that your mount your EFI partition inside your chroot before installing grub if your using a efi setup.
If you're in the mounted image this should look like /dev/loop0
grub-install /dev/sdcset default=0 set timeout=5
insmod part_gpt insmod ext2 set root=(hd0,2) set gfxpayload=1024x768x32
menuentry "GNU/Linux, Linux 6.13.9-lfs-12.3" { linux /boot/vmlinuz-6.13.9-lfs-12.3 root=/dev/sda2 ro }
Grub config from the bash script MyLFS that I based this off. You can get the Part UUID from running blkid and passing in your drive with it's partition id.
blkid /dev/sdc4In it's output you'll see PARTUUID="". What ever is in the string is your part uuid and would go in the grub config. (Best success using this)
set default=0 set timeout=5
insmod ext2
menuentry "GNU/Linux, Linux 6.13.9-lfs-12.3 nomodeset"{ search --no-floppy --label LFSROOT --set=root linux /boot/vmlinuz-6.13.9-lfs-12.3 rootwait root=PARTUUID=ecb913d1-02 ro net.ifnames=0 biosdevname=0 nomodeset }
menuentry "GNU/Linux, Linux 6.13.9-lfs-12.3" { search --no-floppy --label LFSROOT --set=root linux /boot/vmlinuz-6.13.9-lfs-12.3 rootwait root=PARTUUID=ecb913d1-02 ro net.ifnames=0 biosdevname=0 }
if you build dkrecipes you should have grub-mkconfig command. I like to use UUIDs instead of /dev/sdX notation.
cat > /etc/default/grub << "EOF" GRUB_DISABLE_LINUX_UUID="false" GRUB_TIMEOUT=5 GRUB_DISTRIBUTOR="GNU/Linux" GRUB_CMDLINE_LINUX_DEFAULT="" GRUB_CMDLINE_LINUX="" EOF
grub-mkconfig -o /boot/grub/grub.cfgNow we need to unmount the bind mounts. If you also mounted dev/pts that needs to be unmounted before /mnt/usb/dev is.
umount -l /mnt/usb/dev
umount -l /mnt/usb/proc
umount -l /mnt/usb/sys
umount -l /mnt/usb/run
umount -l /mnt/usb/
syncTo try this in qemu you must also umount your new root fs.
umount /mnt/usbif you were working with the image file also run
losetup -d /dev/loop0Remember my drive was /dev/sdc. Make sure to pass in the right drive or lfs.img.
When creating the grub config, the system /dev/ is mounted. However, when booting the drive directly in qemu like done below it becomes the only drive. Thus I had to edit the grub config and change /dev/sdc4 to /dev/sda4. eg. linux /boot/vmlinuz-6.13.9-lfs-12.3 root=/dev/sda3 ro
qemu-system-x86_64 -m 2048 -enable-kvm -cpu host -hda /dev/sdc -boot order=d -vga stdIf you hardware supports it you can add in place of "-vga std"
qemu-system-x86_64 -m 2048 -enable-kvm -cpu host -hda /dev/sdc -boot order=d -device virtio-vga-gl -display gtk,gl=onin builder.py you'll find these set
# Commet these lines out if you want to build packages for your system
"CFLAGS" : "-march=x86-64 -mtune=generic -O2",
"CXXFLAGS" : "-march=x86-64 -mtune=generic -O2",
# Build generic binaries
env["CFLAGS"] = "-march=x86-64 -mtune=generic -O2"
env["CXXFLAGS"] = "-march=x86-64 -mtune=generic -O2"
Phase 1: Cross toolchain
Phase 2: Temp tools /tools
Phase 3: (Entering chroot for first time) Temp system
Phase 4/5: By default the build tool will seek to build all package recipes. Packages in phases 1-3 are critical and the whole build system will crash.
Optionally all packages in the recipes directory can have an optional critical tag. See any base package in LFS build. -- If present this tag will halt the whole build.
Otherwise If a package fails to build, the build system will move on to the next package in the que. -- mylfs will skip all packages that had a required/runtime dep fail to build.
Phase 5 All packages in the recipes directory beyond the basic LFS build are Phase 5. If you pass in recipes to the program or just replace the recipes folder with your own it will keep building all packages and skip failed packages and packages that have missing dependencies.
by default the mylfs will look for a recipes directory in the location it was executed from.
$ ls
logs mnt recipes mylfsIn the recipes folder are bootstrap, [0-9] & [A-z]
If you want a custom kernel config, place a .config as config [do not dot] for the version of the kernel you are building and place it next to the template.yml in the linux recipe directory..
recipes/ └── bootstrap/ └── phase1/ └──glibc ├── source # required - Source is extract here ├── template.yaml # required ├── source.tar.gz # optional ├── patches/ # optional ├── static/ # optional └── config_templates/ # optional
recipes/ └── b/ └── bash/ ├── source # required ├── template.yaml # required ├── source.tar.gz # optional ├── patches/ # optional ├── static/ # optional └── config_templates/ # optional
After doing 100 of these manually for the base install. What I would recommend is to pass an example in chatgpt and give it a link. The build commands and descriptions aren't always right. However, it's a lot faster than manually creating every template.yml file. Especially if you're adding a lot of packages in your recipes folder.
name: # Required - package name version: # Required - package version release: # Optional - for internal versioning (e.g., release bump) url: # Optional - source tarball URL sha256: # Optional - checksum for source verification license: # Required or strongly recommended - license identifier summary: # Optional - short description homepage: # Optional - Link to their website description: # Optional - long multi-line description phase: # Optional - phase1, phase2, phase3, phase4, phase5 order: # Optional - integer ordering (bootstrap only) critical: # Optional - boolean -- (I don't think this works in the current build) builddeps: # Optional - list of build dependencies In phase 5 any phase 4 package with builddeps will be rebuilt taking into account build tools capability to detect dependencies and use them. -- Phase 4 packages that omit it will not be rebuilt rundeps: # Optional - packages that need to be present at run time. (If package is marked as critical if its rundeps are not built it will fail.)
This is ignored in phase 4. In phase 5 all packages will be rebuilt with
dependency awareness from the builddeps.
buildsteps: # Required - preparation steps. Everything bellow is a sh script (bash) for building cleanup: false # Optional - Tells the build system to not delete the extract source on a successful build. It should be noted if your run sudo ./mylfs-py --start-phase It will refresh the recipes folder and deleted the saved source. If it's important you should back it up. If you don't have a clean up tag. The default is to cleanup extracted source
{name} # Optional - Allows the name to be used as a variable {version} # Optional - Allows the version to be used as a variable
$var # In the shell script section all vars must be defined. -- The appropriate LFS variables like $LFS or $LFS_TGT will be passed at each stage to avoid repetition in each script
name: glibc version: 2.39 release: 1 url: https://ftp.gnu.org/gnu/glibc/glibc-2.39.tar.xz homepage: https://www.gnu.org/software/binutils/
license: LGPL-2.1-or-later summary: GNU C Library description: | The GNU C Library provides the core libraries for the GNU system and GNU/Linux systems. This includes essential facilities like libc, crypt, math, and others. phase: 2 order: 3 critical: true builddeps:
- linux-headers
- gcc buildsteps: | mkdir build cd build ../configure --prefix=/tools
name: version: release: url: homepage:
license: summary: description: |
phase: 4 # Unless you want to modify the base LFS your packages should be phase 5 order: # phase 1-4 only critical: true builddeps: [] # if need
buildsteps: |
pip3 install pyyaml pip3 install requests pip3 install sqlalchemy
[] - Rsync [] - tar [] - wget [] - unzip [] - chroot [] - gnu make [] - autotools [] - ninja [] - meson [] - zig