Implement system-update feature

The `system-update` and `install-update` scripts are the same as from
Aimee OS v1, with references to Gentoo replaced, of course.  We need
some additional kernel features in order to mount the firmware partition
and update the GRUB environment file, and of course the `grub-editenv`
tool itself.  We also need `wget` for now, since that's how the tool
downloads the specified update file from the network.

Eventually, `system-update` will be replaced by a much more robust tool,
with package URL discovery, signature verification, etc.  The shell
script will do for now while development is still proceeding rapidly.
master
Dustin 2025-08-30 11:15:32 -05:00
parent 7e5a83ba28
commit 821ea84ea7
7 changed files with 270 additions and 0 deletions

1
ci/Jenkinsfile vendored
View File

@ -65,6 +65,7 @@ pipeline {
'firmware.img.zst',
'rootfs.squashfs',
'sdcard.img.zst',
'update.tar.zst',
].join(','))
}
}

View File

@ -12,7 +12,12 @@ define AIMEEOS_LINUX_CONFIG_FIXUPS
$(call KCONFIG_ENABLE_OPT,CONFIG_BLK_DEV_INITRD)
$(call KCONFIG_ENABLE_OPT,CONFIG_BTRFS_FS)
$(call KCONFIG_ENABLE_OPT,CONFIG_EFI)
$(call KCONFIG_ENABLE_OPT,CONFIG_MSDOS_FS)
$(call KCONFIG_ENABLE_OPT,CONFIG_NLS_CODEPAGE_437)
$(call KCONFIG_ENABLE_OPT,CONFIG_NLS_ISO8859_1)
$(call KCONFIG_ENABLE_OPT,CONFIG_NLS_UTF8)
$(call KCONFIG_ENABLE_OPT,CONFIG_SQUASHFS)
$(call KCONFIG_ENABLE_OPT,CONFIG_VFAT_FS)
endef
LINUX_KCONFIG_FIXUP_CMDS += $(AIMEEOS_LINUX_CONFIG_FIXUPS)
@ -71,6 +76,8 @@ $(BR2_EXTERNAL_AIMEEOS_PATH)/boot/grub2/gen-grub-cfg.sh $(AIMEEOS_KERNEL_FILENAM
endef
LINUX_TARGET_FINALIZE_HOOKS += AIMEEOS_GEN_GRUB_CFG
BR2_ROOTFS_POST_IMAGE_SCRIPT += $(BR2_EXTERNAL_AIMEEOS_PATH)/update/make-package.sh
endif
include $(sort $(wildcard $(BR2_EXTERNAL_AIMEEOS_PATH)/package/*/*.mk))

View File

@ -6,3 +6,5 @@ config BR2_PACKAGE_AIMEE_OS_UTILS
select BR2_PACKAGE_UTIL_LINUX_MOUNTPOINT
select BR2_PACKAGE_UTIL_LINUX_PARTX
select BR2_PACKAGE_UTIL_LINUX_SWITCH_ROOT
select BR2_PACKAGE_WGET
select BR2_TARGET_GRUB2_INSTALL_TOOLS

View File

@ -12,6 +12,13 @@ AIMEE_OS_UTILS_DEPENDENCIES = \
AIMEE_OS_UTILS_SOURCE =
define AIMEE_OS_UTILS_INSTALL_TARGET_CMDS
$(INSTALL) -D -m u=rwx,go=rx \
$(AIMEE_OS_UTILS_PKGDIR)/system-update.sh \
$(TARGET_DIR)/usr/sbin/system-update
mkdir -p $(TARGET_DIR)/boot/efi
endef
define AIMEE_OS_UTILS_INSTALL_INIT_SYSTEMD
$(INSTALL) -D -m u=rw,go=r \
$(AIMEE_OS_UTILS_PKGDIR)/var.mount \

View File

@ -0,0 +1,135 @@
#!/bin/sh
# vim: set sw=4 ts=4 sts=4 et :
cleanup() {
cd /
if [ -n "${workdir}" ] && [ "${workdir}" != / ]; then
rm -rf "${workdir}"
fi
unset workdir
}
die() {
rc=$?
if [ $rc -eq 0 ]; then
rc=1
fi
error "$@"
exit $rc
}
error() {
if [ $# -eq 1 ]; then
echo "$1" >&2
elif [ $# -gt 1 ]; then
printf "$@" >&2
fi
}
extract_update() {
zstd -dc update.tar.zstd | tar -x \
|| die 'Could not extract update source'
sha256sum -c digests \
|| die 'Invalid update source: checksum mismatch'
}
fetch_update() {
wget -O update.tar.zstd "$1"
}
get_root() {
set -- $(cat /proc/cmdline)
while [ $# -gt 0 ]; do
case "$1" in
root=*)
_root=${1#root=}
;;
esac
shift
done
echo $(findfs "${_root}")
}
get_partlabel() {
blkid -o value -s PARTLABEL "$1"
}
help() {
usage
}
info() {
if [ $# -eq 1 ]; then
echo "$1" >&2
elif [ $# -gt 1 ]; then
printf "$@" >&2
fi
}
usage() {
printf 'usage: %s source_url\n' "${0##*/}"
}
while [ $# -gt 0 ]; do
case "$1" in
--help)
help
exit 0
;;
*)
if [ -z "${source_url}" ]; then
source_url="$1"
else
usage >&2
exit 2
fi
;;
esac
shift
done
if [ -z "${source_url}" ]; then
usage >&2
exit 2
fi
root=$(get_root)
partlabel=$(get_partlabel "${root}")
case "${partlabel}" in
rootfs-a)
newpartlabel=rootfs-b
;;
rootfs-b)
newpartlabel=rootfs-a
;;
*)
die \
'Unsupported system configuration: invalid rootfs partition label: %s\n' \
"${partlabel}" >&2
esac
newroot=$(findfs PARTLABEL="${newpartlabel}")
if [ -z "${newroot}" ]; then
die 'Could not find partition with label %s\n' "${partlabel}"
fi
info 'Current rootfs: %s (%s)\n' "${partlabel}" "${root}"
info 'New rootfs: %s (%s)\n' "${newpartlabel}" "${newroot}"
trap cleanup INT TERM QUIT EXIT
workdir=$(mktemp -d)
cd "${workdir}"
fetch_update "${source_url}" || die 'Failed to fetch update source'
extract_update || die 'Failed to extact update source'
./install "${newroot}" || die 'Error installing system update'
printf 'Do you want to reboot now? [y/N] '
read confirm
case "${confirm}" in
[yY]|[yY][eE][sS])
systemctl reboot
;;
*)
info 'A reboot is required to complete the update'
;;
esac

101
update/install-update.sh Executable file
View File

@ -0,0 +1,101 @@
#!/bin/sh
# vim: set sw=4 ts=4 sts=4 et :
EFIMOUNT=/boot/efi
GRUBENV=${EFIMOUNT}/EFI/BOOT/grubenv
die() {
rc=$?
if [ $rc -eq 0 ]; then
rc=1
fi
error "$@"
exit $rc
}
error() {
printf 'ERROR: '
info "$@"
}
get_partuuid() {
blkid -o value -s PARTUUID "$1"
}
info() {
if [ $# -eq 1 ]; then
echo "$1" >&2
elif [ $# -gt 1 ]; then
printf "$@" >&2
fi
}
set_default_boot() {
_rc=0
mkdir -p newroot || return
mount -oro "$1" newroot || return
_partuuid=$(get_partuuid "$1")
_id=id-${_partuuid}
printf 'Setting default boot entry to %s\n' "${_id}"
grub-editenv "${GRUBENV}" set "default=${_id}" || rc=$?
umount newroot
return $rc
}
warn() {
printf 'WARNING: '
info "$@"
}
write_firmware() {
_rc=0
_esp=$(findfs PARTLABEL='EFI System Partition')
if [ -z "${_esp}" ]; then
error 'Could not identify EFI System Partition'
return 1
fi
if ! mountpoint -q "${EFIMOUNT}"; then
mount -o ro "${_esp}" "${EFIMOUNT}" \
|| warn 'Failed to mount EFI System Partition'
fi
if [ -f "${GRUBENV}" ]; then
info 'Saving current GRUB environment ...'
cp "${GRUBENV}" grubenv \
|| warn 'Failed to save GRUB environment'
fi
if mountpoint -q "${EFIMOUNT}"; then
umount "${EFIMOUNT}" || return
fi
info 'Writing firmware image to EFI System Partition (%s) ...\n' "${_esp}"
dd if=firmware.img of="${_esp}" bs=1M || _rc=$?
if [ $_rc -eq 0 ]; then
mount -orw "${_esp}" "${EFIMOUNT}" || rc=$?
fi
if [ $_rc -eq 0 ]; then
if [ -f grubenv ]; then
printf 'Restoring GRUB environment ...\n'
cp grubenv "${GRUBENV}" || _rc=$?
fi
fi
return $_rc
}
write_rootfs() {
printf 'Writing rootfs image to %s ...\n' "$1"
dd if=rootfs.squashfs of="$1" bs=1M
}
rc=0
newroot="$1"
write_rootfs "${newroot}" || die 'Failed to write new rootfs image to disk'
write_firmware || die 'Failed to write new firmware image to disk'
if ! set_default_boot "${newroot}"; then
rc=$?
error 'Failed to set default boot option'
fi
if [ $rc -eq 0 ]; then
info 'Successfully installed update'
fi
exit $rc

17
update/make-package.sh Executable file
View File

@ -0,0 +1,17 @@
#!/bin/sh
# vim: set sw=4 ts=4 sts=4 et :
UPDATE_PACKAGE=update.tar.zstd
SRCDIR=$(realpath "${0%/*}")
cd "${BINARIES_DIR}"
printf 'Creating %s/%s\n' "${BINARIES_DIR}" "${UPDATE_PACKAGE}" >&2
sha256sum firmware.img > digests || exit
sha256sum rootfs.squashfs >> digests || exit
cp -u "${SRCDIR}"/install-update.sh install || exit
tar -c --zstd -f "${UPDATE_PACKAGE}" \
digests \
firmware.img \
rootfs.squashfs \
install \
|| exit