From c5f41a4e5ef7524c3182630290bbd27de59ec6b6 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Thu, 28 Aug 2025 21:37:44 -0500 Subject: [PATCH] Begin persistent storage implementation Most of the logic in the `init-storage` script is the same as it was in Aimee OS v1 (Gentoo). The major difference is now we are initializing the data volume in the initramfs instead of in the real OS. This allows us to make all of `/etc` writable via OverlayFS, instead of having only certain sub-directories writable via bind-mounts. Buildroot doesn't really have any tools for building an initramfs, unfortunately. It does have a bit of infrastructure for running `dracut`, but I'd really rather avoid having that much complexity in the initramfs; all we need is to run the `init-storage` script and then switch root. Instead, the `mkinitramfs.sh` script, called in the post-build stage, creates the CPIO archive from files in the target directory. The only particularly interesting bit is how it resolves shared library dependencies, to make sure the appropriate resources are available for the requisite commands. I briefly considered building a statically-linked BusyBox just for the initramfs. Since it doesn't provide several important tools like `btrfs`/`mkfs.btrfs`, I had to implement the dynamic link resolution function anyway. It made sense, then, to copy Dash and the necessary Coreutils binaries themselves. --- Config.in | 3 + boot/grub2/gen-grub-cfg.sh | 1 + boot/initramfs/init-storage.sh | 165 +++++++++++++++++++++++ boot/initramfs/initramfs-init.sh | 27 ++++ boot/mkinitramfs.sh | 113 ++++++++++++++++ external.mk | 21 +++ package/aimee-os-utils/Config.in | 8 ++ package/aimee-os-utils/aimee-os-utils.mk | 21 +++ package/aimee-os-utils/var.mount | 12 ++ 9 files changed, 371 insertions(+) create mode 100755 boot/initramfs/init-storage.sh create mode 100755 boot/initramfs/initramfs-init.sh create mode 100755 boot/mkinitramfs.sh create mode 100644 package/aimee-os-utils/Config.in create mode 100644 package/aimee-os-utils/aimee-os-utils.mk create mode 100644 package/aimee-os-utils/var.mount diff --git a/Config.in b/Config.in index 8632387..351303a 100644 --- a/Config.in +++ b/Config.in @@ -15,6 +15,7 @@ config AIMEEOS select BR2_PACKAGE_HOST_MTOOLS select BR2_TARGET_GRUB2 select BR2_PACKAGE_HOST_ZSTD + select BR2_PACKAGE_AIMEE_OS_UTILS help Enable all Aimee OS features. @@ -35,3 +36,5 @@ config AIMEEOS_DEFAULT_ROOTFLAGS string "Default kernel command line argumens" help Additional command line arguments to pass to the kernel by default. + +source "$BR2_EXTERNAL_AIMEEOS_PATH/package/aimee-os-utils/Config.in" diff --git a/boot/grub2/gen-grub-cfg.sh b/boot/grub2/gen-grub-cfg.sh index 37adca2..ca34682 100755 --- a/boot/grub2/gen-grub-cfg.sh +++ b/boot/grub2/gen-grub-cfg.sh @@ -9,5 +9,6 @@ cat > "${TARGET_DIR}"/boot/grub.cfg <&2 + mount -o subvol="${vol#/}" "${dev}" "${tmpdir}" || exit + cp -au${VERBOSE+v} /sysroot/${vol#/}/. "${tmpdir}" || exit + umount "${tmpdir}" +} + +format_dev() { + dev="$1" + partno=$(partition_number "${dev}") + if [ -n "${partno}" ]; then + disk="$(get_disk "${dev}")" + if [ -n "${disk}" ]; then + printf 'Resizing partition %d on disk %s\n' \ + "${partno}" \ + "${disk}" \ + >&2 + resize_partition "${disk}" "${partno}" + else + printf 'Could not find disk for device %s\n' \ + "${dev}" \ + >&2 + fi + fi + + printf 'Creating BTRFS filesystem on %s\n' "${dev}" >&2 + mkfs.btrfs --quiet "${dev}" || exit + + mount "${dev}" "${tmpdir}" || exit + for vol in ${SUBVOLUMES}; do + mkdir -p "${tmpdir}${vol%/*}" || exit + btrfs subvolume create "${tmpdir}${vol}" || exit + done + relabel_all + umount "${dev}" || exit +} + +get_disk() { + _syspath=/sys/class/block/${1##*/} + [ -d "${_syspath}" ] || return 1 + if [ ! -f "${_syspath}"/partition ]; then + readlink -f "${1}" + return $? + fi + _disk=$(readlink -f "${_syspath}"/..) + if [ -n "${_disk}" ]; then + printf '/dev/%s\n' "${_disk##*/}" + return 0 + fi + return 1 +} + +has_fs() { + dev="$1" + fstype=$(blkid -o value -s TYPE "${dev}") + [ -n "${fstype}" ] +} + +last_partition() { + cat /sys/class/block/"${1##*/}"/*/partition \ + | sort -n \ + | tail -n1 +} + +partition_number() { + cat /sys/class/block/${1##*/}/partition +} + +relabel_all() { + if [ ! -d /sys/fs/selinux ] || [ ! -f /etc/selinux/config ]; then + return + fi + selinuxtype=$(. /etc/selinux/config && echo ${SELINUXTYPE}) + find "${tmpdir}" | \ + setfiles \ + -v \ + -F \ + -m \ + -r "${tmpdir}" \ + -s \ + /etc/selinux/${selinuxtype}/contexts/files/file_contexts +} + +resize_partition() { + _disk="${1}" + _part="${2}" + _lastpart=$(last_partition "${_disk}") + if [ "${_part}" -ne "${_lastpart}" ]; then + printf 'Cannot resize %s, it is not the last partition on the disk\n' \ + "${_dev}" \ + >&2 + return 1 + fi + _uuid=$(sfdisk --part-uuid "${_disk}" "${_part}") || return $? + _type=$(sfdisk --part-type "${_disk}" "${_part}") || return $? + _label=$(sfdisk --part-label "${_disk}" "${_part}") || return $? + sfdisk --delete "${_disk}" "${_part}" || return $? + printf 'type=%s, uuid=%s, name="%s"\n' \ + "${_type}" \ + "${_uuid}" \ + "${_label}" \ + | sfdisk -N "${_part}" "${_disk}" --quiet --force \ + || return $? + partx -u "${_disk}" +} + +setup_etc() { + dev="$1" + + echo 'Initializing /etc overlay' >&2 + mkdir -p /run/aimeeos/etc || return + mount -o subvol=etc "${dev}" /run/aimeeos/etc || return + mkdir -p /run/aimeeos/etc/rw /run/aimeeos/etc/work || return + mount -t overlay \ + -o lowerdir=/sysroot/etc,upperdir=/run/aimeeos/etc/rw,workdir=/run/aimeeos/etc/work \ + overlay \ + /sysroot/etc +} + +rootdev=$(findfs "$1") +datapart=$(findfs "${2:-PARTLABEL=aimeeos-data}") +if [ -b "${datapart}" ]; then + printf 'Found data partition: %s\n' "${datapart}" >&2 +else + echo 'Could not identify data partition' >&2 + exit 1 +fi + +trap cleanup INT TERM QUIT EXIT +tmpdir=/run/storinit +mkdir -p "${tmpdir}" + +if ! has_fs "${datapart}"; then + format_dev "${datapart}" +fi + +mkdir -p /sysroot +mount -o ro "${rootdev}" /sysroot || exit + +setup_etc "${datapart}" +copy_vol "${datapart}" /var +exit 0 diff --git a/boot/initramfs/initramfs-init.sh b/boot/initramfs/initramfs-init.sh new file mode 100755 index 0000000..b45e7a4 --- /dev/null +++ b/boot/initramfs/initramfs-init.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +mkdir -p \ + /dev \ + /proc \ + /run \ + /sys \ + /sysroot \ + && : + +mount -t devtmpfs devtmpfs /dev +mount -t proc proc /proc +mount -t sysfs sysfs /sys +mount -t tmpfs tmpfs /run + +set -- $(cat /proc/cmdline) +for arg; do + case "${arg}" in + root=*) + root=${arg#root=} + ;; + esac +done + +init-storage "${root}" + +exec switch_root /sysroot /sbin/init diff --git a/boot/mkinitramfs.sh b/boot/mkinitramfs.sh new file mode 100755 index 0000000..b12c5cc --- /dev/null +++ b/boot/mkinitramfs.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +if [ $(id -u) != 0 ]; then + exec "${HOST_DIR}"/bin/fakeroot "$0" "$@" +fi + +export PATH="${HOST_DIR}:${PATH}" + +TARGET_DIR=$1 +WORKDIR=$(mktemp -d) +OUTDIR="${WORKDIR}"/initramfs +LIB_CACHE="${WORKDIR}"/libs.cache +PKGDIR="${0%/*}" + +trap 'rm -rf "${WORKDIR}"' INT QUIT TERM EXIT + +cache_libs() { + find \ + "${TARGET_DIR}"/usr/lib \ + "${TARGET_DIR}"/usr/lib64 \ + \( -type f -o -type l \) \ + -name '*.so*' \ + | sed "s@${TARGET_DIR}@@" \ + > "${LIB_CACHE}" +} + +bin_install() { + for arg; do + arg=${arg#/} + [ -e "${OUTDIR}/${arg}" ] && continue + mkdir -p "${OUTDIR}/${arg%/*}" + cp -a "${TARGET_DIR}/${arg}" "${OUTDIR}/${arg}" + if [ -h "${TARGET_DIR}/${arg}" ]; then + bin_install "$(realpath --relative-to "${TARGET_DIR}" "${TARGET_DIR}/${arg}")" + elif [ -f "${TARGET_DIR}/${arg}" ]; then + readelf --dynamic "${TARGET_DIR}/${arg}" \ + | awk '$2=="(NEEDED)"{gsub(/\[|\]/,"",$5); print $5}' \ + | while IFS= read -r lib; do + path="$(grep "${lib}"'$' "${LIB_CACHE}")" + if [ -z "${path}" ]; then + printf 'ERROR could not resolve shared library %s\n' "${lib}" >&2 + return 1 + fi + bin_install "${path}" + done + elif [ ! -e "${TARGET_DIR}/${arg}" ]; then + printf 'ERROR could not find /%s to copy\n' "${arg}" >&2 + return 1 + fi + done +} + +mk_skel() { + mkdir -p "${OUTDIR}"/dev + mknod -m 0622 "${OUTDIR}"/dev/console c 5 1 + + mkdir -p \ + "${OUTDIR}"/usr \ + "${OUTDIR}"/usr/bin \ + "${OUTDIR}"/usr/sbin \ + "${OUTDIR}"/usr/lib + ln -s usr/bin "${OUTDIR}"/bin + ln -s usr/sbin "${OUTDIR}"/sbin + ln -s usr/lib "${OUTDIR}"/lib + if [ -h "${TARGET_DIR}"/usr/lib64 ]; then + cp -P "${TARGET_DIR}"/usr/lib64 "${OUTDIR}"/usr + fi + if [ -h "${TARGET_DIR}"/lib64 ]; then + cp -P "${TARGET_DIR}"/lib64 "${OUTDIR}" + fi +} + +rm -rf "${OUTDIR}" + +mk_skel || exit +cache_libs || exit + +bin_install \ + /bin/cat \ + /bin/cp \ + /bin/ls \ + /bin/mkdir \ + /bin/mount \ + /bin/rm \ + /bin/sh \ + /bin/sort \ + /bin/tail \ + /bin/umount \ + /usr/bin/btrfs \ + /usr/bin/mkfs.btrfs \ + /usr/bin/mountpoint \ + /usr/bin/readlink \ + /usr/sbin/blkid \ + /usr/sbin/findfs \ + /usr/sbin/partx \ + /usr/sbin/sfdisk \ + /usr/sbin/switch_root \ + || exit + +if [ -e "${TARGET_DIR}"/usr/bin/setfiles ]; then + bin_install /usr/bin/setfiles || exit +fi + +install "${PKGDIR}"/initramfs/initramfs-init.sh "${OUTDIR}"/init || exit +install "${PKGDIR}"/initramfs/init-storage.sh "${OUTDIR}"/usr/bin/init-storage || exit + +mkdir -p "${TARGET_DIR}"/boot + +(cd "${OUTDIR}" && find . -mindepth 1 \ + | LC_ALL=C sort \ + | cpio --reproducible --quiet -o -H newc \ + | zstd \ +) > "${TARGET_DIR}"/boot/initramfs.img.zst diff --git a/external.mk b/external.mk index 834c04b..f33020b 100644 --- a/external.mk +++ b/external.mk @@ -1,3 +1,20 @@ +ifeq ($(AIMEEOS),y) + +# Disable the default fstab +SKELETON_INIT_SYSTEMD_ROOT_RO_OR_RW = +# Disable the default var.mount +SKELETON_INIT_SYSTEMD_ROOTFS_PRE_CMD_HOOKS = + +# Enable required kernel options for Aimee OS storage +define AIMEEOS_LINUX_CONFIG_FIXUPS +$(call KCONFIG_ENABLE_OPT,CONFIG_BTRFS_FS) +$(call KCONFIG_ENABLE_OPT,CONFIG_BLK_DEV_INITRD) +endef +LINUX_KCONFIG_FIXUP_CMDS += $(AIMEEOS_LINUX_CONFIG_FIXUPS) + +# Generate the initramfs image after building the target +BR2_ROOTFS_POST_BUILD_SCRIPT += $(BR2_EXTERNAL_AIMEEOS_PATH)/boot/mkinitramfs.sh + # Overwrite the grub.cfg provided by Buildroot with our own. define AIMEEOS_GRUB2_INSTALL_IMAGES_CMDS $(foreach tuple, $(GRUB2_TUPLES-y), \ @@ -27,3 +44,7 @@ define AIMEEOS_GEN_GRUB_CFG $(BR2_EXTERNAL_AIMEEOS_PATH)/boot/grub2/gen-grub-cfg.sh $(AIMEEOS_KERNEL_FILENAME) endef LINUX_TARGET_FINALIZE_HOOKS += AIMEEOS_GEN_GRUB_CFG + +endif + +include $(sort $(wildcard $(BR2_EXTERNAL_AIMEEOS_PATH)/package/*/*.mk)) diff --git a/package/aimee-os-utils/Config.in b/package/aimee-os-utils/Config.in new file mode 100644 index 0000000..2807f2b --- /dev/null +++ b/package/aimee-os-utils/Config.in @@ -0,0 +1,8 @@ +config BR2_PACKAGE_AIMEE_OS_UTILS + bool + select BR2_PACKAGE_BTRFS_PROGS + select BR2_PACKAGE_UTIL_LINUX + select BR2_PACKAGE_UTIL_LINUX_BINARIES + select BR2_PACKAGE_UTIL_LINUX_MOUNTPOINT + select BR2_PACKAGE_UTIL_LINUX_PARTX + select BR2_PACKAGE_UTIL_LINUX_SWITCH_ROOT diff --git a/package/aimee-os-utils/aimee-os-utils.mk b/package/aimee-os-utils/aimee-os-utils.mk new file mode 100644 index 0000000..7952bee --- /dev/null +++ b/package/aimee-os-utils/aimee-os-utils.mk @@ -0,0 +1,21 @@ +################################################################################ +# +# Aimee OS Utils +# +################################################################################ + +AIMEE_OS_UTILS_VERSION = 2.0 +AIMEE_OS_UTILS_LICENSE = GPL-3.0+ +AIMEE_OS_UTILS_DEPENDENCIES = \ + host-fakeroot \ + btrfs-progs \ + +AIMEE_OS_UTILS_SOURCE = + +define AIMEE_OS_UTILS_INSTALL_INIT_SYSTEMD + $(INSTALL) -D -m u=rw,go=r \ + $(AIMEE_OS_UTILS_PKGDIR)/var.mount \ + $(TARGET_DIR)/usr/lib/systemd/system/var.mount +endef + +$(eval $(generic-package)) diff --git a/package/aimee-os-utils/var.mount b/package/aimee-os-utils/var.mount new file mode 100644 index 0000000..17a27bc --- /dev/null +++ b/package/aimee-os-utils/var.mount @@ -0,0 +1,12 @@ +[Unit] +Description=/var +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target +After=swap.target + +[Mount] +What=PARTLABEL=aimeeos-data +Where=/var +Type=btrfs +Options=subvol=var,nosuid,nodev,noexec