diff --git a/ci/Jenkinsfile b/ci/Jenkinsfile index 569efe0..69a618b 100644 --- a/ci/Jenkinsfile +++ b/ci/Jenkinsfile @@ -65,6 +65,7 @@ pipeline { 'firmware.img.zst', 'rootfs.squashfs', 'sdcard.img.zst', + 'update.tar.zst', ].join(',')) } } diff --git a/external.mk b/external.mk index cc4edfd..ab860c0 100644 --- a/external.mk +++ b/external.mk @@ -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)) diff --git a/package/aimee-os-utils/Config.in b/package/aimee-os-utils/Config.in index 2807f2b..04027ff 100644 --- a/package/aimee-os-utils/Config.in +++ b/package/aimee-os-utils/Config.in @@ -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 diff --git a/package/aimee-os-utils/aimee-os-utils.mk b/package/aimee-os-utils/aimee-os-utils.mk index 7952bee..28dde14 100644 --- a/package/aimee-os-utils/aimee-os-utils.mk +++ b/package/aimee-os-utils/aimee-os-utils.mk @@ -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 \ diff --git a/package/aimee-os-utils/system-update.sh b/package/aimee-os-utils/system-update.sh new file mode 100755 index 0000000..5e9c798 --- /dev/null +++ b/package/aimee-os-utils/system-update.sh @@ -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 diff --git a/update/install-update.sh b/update/install-update.sh new file mode 100755 index 0000000..bc01dd8 --- /dev/null +++ b/update/install-update.sh @@ -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 diff --git a/update/make-package.sh b/update/make-package.sh new file mode 100755 index 0000000..de0879a --- /dev/null +++ b/update/make-package.sh @@ -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