Begin persistent storage implementation
All checks were successful
Aimee OS/aimee-os/pipeline/head This commit looks good

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.
This commit is contained in:
2025-08-28 21:37:44 -05:00
parent 05dd3810c9
commit 44b2ce8a99
9 changed files with 371 additions and 0 deletions

165
boot/initramfs/init-storage.sh Executable file
View File

@@ -0,0 +1,165 @@
#!/bin/sh
# vim: set sw=4 ts=4 sts=4 et :
SUBVOLUMES='
/var
/var/log
/var/tmp
/etc
'
cleanup() {
if [ -n "${tmpdir}" ] && [ "${tmpdir}" != / ]; then
if mountpoint -q "${tmpdir}"; then
umount "${tmpdir}"
fi
rm -rf "${tmpdir}"
unset tmpdir
fi
}
copy_vol() {
dev="$1"
vol=$2
printf 'Copying %s contents to data volume\n' "${vol}" >&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

View File

@@ -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