Compare commits

..

10 Commits

Author SHA1 Message Date
Dustin c5f41a4e5e Begin persistent storage implementation
Aimee OS/aimee-os/pipeline/head This commit looks good Details
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.
2025-08-28 21:51:42 -05:00
Dustin 145cca736f configs/qemu: Enable debug shell on ttyAMA0
We set the default kernel command-line arguments to tell systemd to
spawn a debug shell on the serial console, instead of the default getty.
This will allow tests to run commands directly on the console, without
any authentication, etc.
2025-08-28 21:47:39 -05:00
Dustin 973f62a654 grub2: Support setting rootflags in config
The `AIMEEOS_DEFAULT_ROOTFLAGS` kconfig option can be used to set the
default value for the `rootflags` GRUB2 environment variable.  The value
of this variable will be passed along as command-line arguments to
whatever kernel is chosen at boot.

Since post-image scripts do not have access to kconfig option values, we
need to build the GRUB2 environment file in the
`GRUB2_INSTALL_IMAGES_CMDS` script fragment instead.
2025-08-28 21:47:39 -05:00
Dustin cf053ee85c ci: Add CUSTOM_TARGET parameter
If a value is provided for the `CUSTOM_TARGET` parameter, the specified
target will be built first, before building the filesystem images.  This
provides a way to e.g. rebuild a specific package.
2025-08-28 21:47:39 -05:00
Dustin 0b535a2732 boot/grub2: Set terminfo dumb
GRUB seems to have trouble with line drawing on both the Raspberry Pi
and QEMU.
2025-08-28 21:47:39 -05:00
Dustin b67f050532 configs/*: Install GRUB terminfo module
So we can set `terminfo dumb` and hopefully get better output over the
serial console.
2025-08-28 21:47:39 -05:00
Dustin 7f3c9ffe5b gen-grub-cfg: Support different kernel file names
Different architectures and/or configurations can use a different name
for the Linux kernel image file.  The `gen-grub-cfg.sh` shell script
needs to be able to identify the correct file name in the GRUB
configuration fragment it embeds in the root filesystem image.
2025-08-28 21:47:39 -05:00
Dustin 9562b7f9fd board/qemu: Generate sdcard.img with post-image
In order to test AimeeOS A/B filesystem updates, we need a full disk
image just like the Raspberry Pi variant has.
2025-08-28 21:47:39 -05:00
Dustin 2d05c3a5f1 wip: ci: Add Jenkins pipeline 2025-08-28 21:47:26 -05:00
Dustin 841b64bac2 configs: Add QEMU aarch64 config for testing 2025-08-28 21:47:24 -05:00
11 changed files with 372 additions and 4 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/_build

View File

@ -15,6 +15,7 @@ config AIMEEOS
select BR2_PACKAGE_HOST_MTOOLS select BR2_PACKAGE_HOST_MTOOLS
select BR2_TARGET_GRUB2 select BR2_TARGET_GRUB2
select BR2_PACKAGE_HOST_ZSTD select BR2_PACKAGE_HOST_ZSTD
select BR2_PACKAGE_AIMEE_OS_UTILS
help help
Enable all Aimee OS features. Enable all Aimee OS features.
@ -35,3 +36,5 @@ config AIMEEOS_DEFAULT_ROOTFLAGS
string "Default kernel command line argumens" string "Default kernel command line argumens"
help help
Additional command line arguments to pass to the kernel by default. Additional command line arguments to pass to the kernel by default.
source "$BR2_EXTERNAL_AIMEEOS_PATH/package/aimee-os-utils/Config.in"

View File

@ -9,5 +9,6 @@ cat > "${TARGET_DIR}"/boot/grub.cfg <<EOF
menuentry "Aimee OS ${vers} (Buildroot ${br2_vers}) on \$dev" --class gnu-linux --id "id-\$partuuid" "\$dev" "\$partuuid" { menuentry "Aimee OS ${vers} (Buildroot ${br2_vers}) on \$dev" --class gnu-linux --id "id-\$partuuid" "\$dev" "\$partuuid" {
set root="\$2" set root="\$2"
linux "/boot/${kernel_filename}" root=PARTUUID=\$3 ro \$rootflags linux "/boot/${kernel_filename}" root=PARTUUID=\$3 ro \$rootflags
initrd "/boot/initramfs.img.zst"
} }
EOF EOF

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

113
boot/mkinitramfs.sh Executable file
View File

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

View File

@ -4,10 +4,6 @@ BR2_GLOBAL_PATCH_DIR="board/qemu/patches"
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
BR2_INIT_SYSTEMD=y BR2_INIT_SYSTEMD=y
BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_AIMEEOS_PATH)/board/qemu/post-image.sh" BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_AIMEEOS_PATH)/board/qemu/post-image.sh"
# BR2_INIT_SYSTEMD_POPULATE_TMPFILES is not set
# BR2_TARGET_ENABLE_ROOT_LOGIN is not set
# BR2_TARGET_GENERIC_GETTY is not set
# BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set
BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/qemu/aarch64-virt/linux.config" BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/qemu/aarch64-virt/linux.config"

View File

@ -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. # Overwrite the grub.cfg provided by Buildroot with our own.
define AIMEEOS_GRUB2_INSTALL_IMAGES_CMDS define AIMEEOS_GRUB2_INSTALL_IMAGES_CMDS
$(foreach tuple, $(GRUB2_TUPLES-y), \ $(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) $(BR2_EXTERNAL_AIMEEOS_PATH)/boot/grub2/gen-grub-cfg.sh $(AIMEEOS_KERNEL_FILENAME)
endef endef
LINUX_TARGET_FINALIZE_HOOKS += AIMEEOS_GEN_GRUB_CFG LINUX_TARGET_FINALIZE_HOOKS += AIMEEOS_GEN_GRUB_CFG
endif
include $(sort $(wildcard $(BR2_EXTERNAL_AIMEEOS_PATH)/package/*/*.mk))

View File

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

View File

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

View File

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