Compare commits

...

12 Commits

Author SHA1 Message Date
Dustin dd6261aa33 Configure network interfaces by default
Aimee OS/aimee-os/pipeline/head This commit looks good Details
For an optimal out-of-the-box experience, we need to automatically
configure any wired network interfaces with DHCP.  `systemd-networkd`
does not do this by default.
2025-08-30 11:21:44 -05:00
Dustin 821ea84ea7 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.
2025-08-30 11:21:44 -05:00
Dustin 7e5a83ba28 configs/*: Move GRUB, kernel config to external.mk
The fewer required items in each defconfig file, the easier they will be
to maintain.
2025-08-29 20:29:43 -05:00
Dustin 44b2ce8a99 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-29 20:17:52 -05:00
Dustin 05dd3810c9 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-29 20:17:52 -05:00
Dustin 8a2d305b04 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-29 20:17:52 -05:00
Dustin 20c92961d6 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-29 20:17:52 -05:00
Dustin 1a33e711bb configs/*: Install GRUB terminfo module
So we can set `terminfo dumb` and hopefully get better output over the
serial console.
2025-08-29 20:13:18 -05:00
Dustin 3a0d599ff6 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-29 20:13:18 -05:00
Dustin 66af20213b 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-29 20:13:18 -05:00
Dustin d41ad4c55c ci: Add Jenkins pipeline 2025-08-29 20:13:14 -05:00
Dustin 841b64bac2 configs: Add QEMU aarch64 config for testing 2025-08-28 21:47:24 -05:00
21 changed files with 834 additions and 9 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/_build

View File

@ -10,8 +10,12 @@ config AIMEEOS
select BR2_PACKAGE_COREUTILS
select BR2_PACKAGE_TAR
select BR2_PACKAGE_ZSTD
select BR2_PACKAGE_HOST_DOSFSTOOLS
select BR2_PACKAGE_HOST_GENIMAGE
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.
@ -25,8 +29,12 @@ config AIMEEOS_RPI
select BR2_TARGET_UBOOT_NEEDS_OPENSSL
select BR2_TARGET_UBOOT_NEEDS_GNUTLS
select BR2_TARGET_GRUB2_ARM_EFI
select BR2_PACKAGE_HOST_DOSFSTOOLS
select BR2_PACKAGE_HOST_GENIMAGE
select BR2_PACKAGE_HOST_MTOOLS
help
Support running Aimee OS on Rasperry Pi devices
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"

1
board/qemu/genimage.cfg Symbolic link
View File

@ -0,0 +1 @@
../raspberrypi3/genimage.cfg

18
board/qemu/post-image.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/sh
set -e
BOARD_DIR=$(dirname "$0")
genimage_tmp=$(mktemp -d)
trap 'rm -rf "${genimage_tmp}"' EXIT
mkdir -p "${genimage_tmp}"/rootfs "${genimage_tmp}"/tmp
cd "${BINARIES_DIR}"
genimage \
--rootpath "${genimage_tmp}/rootfs" \
--tmppath "${genimage_tmp}/tmp" \
--inputpath "${BINARIES_DIR}" \
--outputpath "${BINARIES_DIR}" \
--config "${BOARD_DIR}"/genimage.cfg

View File

@ -3,10 +3,12 @@
vers=${BR2_EXTERNAL_AIMEEOS_VERSION}
br2_vers=${BR2_VERSION_FULL}
kernel_filename=$1
cat > "${TARGET_DIR}"/boot/grub.cfg <<EOF
menuentry "Aimee OS ${vers} (Buildroot ${br2_vers}) on \$dev" --class gnu-linux --id "id-\$partuuid" "\$dev" "\$partuuid" {
set root="\$2"
linux "/boot/zImage" root=PARTUUID=\$3 ro
linux "/boot/${kernel_filename}" root=PARTUUID=\$3 ro \$rootflags
initrd "/boot/initramfs.img.zst"
}
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

75
ci/Jenkinsfile vendored Normal file
View File

@ -0,0 +1,75 @@
pipeline {
parameters {
booleanParam 'CLEAN_BUILD'
string 'CUSTOM_TARGET'
}
options {
disableConcurrentBuilds()
}
agent {
kubernetes {
yamlFile 'ci/podTemplate.yaml'
yamlMergeStrategy merge()
workspaceVolume persistentVolumeClaimWorkspaceVolume(
claimName: 'buildroot-aimeeos'
)
defaultContainer 'build'
}
}
environment {
BR2_CCACHE_DIR = "${env.JENKINS_AGENT_WORKDIR}/br2-ccache"
}
stages {
stage('Clean') {
when {
expression {
return params.CLEAN_BUILD
}
}
steps {
sh 'git clean -fdx'
}
}
stage('Prepare') {
steps {
container('jnlp') {
sh 'if [ ! -d buildroot ]; then git clone https://gitlab.com/buildroot.org/buildroot.git -b 2025.05.x --depth 1; else git -C buildroot pull; fi'
}
}
}
stage('Build') {
steps {
sh 'make -C buildroot O="${PWD}"/_build BR2_EXTERNAL="${PWD}" aimeeos_qemu_aarch64_defconfig'
script {
if (params.CUSTOM_TARGET) {
sh "make -C _build '${CUSTOM_TARGET}'"
}
}
sh 'make -C _build'
}
post {
success {
dir('_build') {
archiveArtifacts('.config')
}
dir('_build/images') {
sh 'zstd -f firmware.img'
sh 'zstd -f sdcard.img'
archiveArtifacts([
'firmware.img.zst',
'rootfs.squashfs',
'sdcard.img.zst',
'update.tar.zst',
].join(','))
}
}
}
}
}
}

19
ci/podTemplate.yaml Normal file
View File

@ -0,0 +1,19 @@
spec:
containers:
- name: build
image: git.pyrocufflink.net/containerimages/buildroot
resources:
limits: &resources
cpu: 6
memory: 12Gi
requests: *resources
volumeMounts:
- mountPath: /etc/ssh/ssh_known_hosts
name: ssh-known-hosts
subPath: ssh_known_hosts
securityContext:
fsGroupChangePolicy: OnRootMismatch
volumes:
- name: ssh-known-hosts
configMap:
name: ssh-known-hosts

View File

@ -0,0 +1,14 @@
BR2_aarch64=y
BR2_TOOLCHAIN_BUILDROOT_VENDOR="aimeeos"
BR2_GLOBAL_PATCH_DIR="board/qemu/patches"
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
BR2_INIT_SYSTEMD=y
BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_AIMEEOS_PATH)/board/qemu/post-image.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/qemu/aarch64-virt/linux.config"
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
# BR2_PACKAGE_BUSYBOX is not set
# BR2_TARGET_ROOTFS_TAR is not set
AIMEEOS=y
AIMEEOS_DEFAULT_ROOTFLAGS="systemd.mask=serial-getty@ttyAMA0 systemd.debug_shell systemd.default_debug_tty=ttyAMA0"

View File

@ -10,7 +10,6 @@ BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_TARBALL=y
BR2_LINUX_KERNEL_CUSTOM_TARBALL_LOCATION="$(call github,raspberrypi,linux,ac69f097e1fba94502cbd36278db204120a37943)/linux-ac69f097e1fba94502cbd36278db204120a37943.tar.gz"
BR2_LINUX_KERNEL_DEFCONFIG="bcm2709"
BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_AIMEEOS_PATH)/kernel/config"
BR2_LINUX_KERNEL_ZSTD=y
BR2_LINUX_KERNEL_DTS_SUPPORT=y
BR2_LINUX_KERNEL_INTREE_DTS_NAME="broadcom/bcm2710-rpi-3-b broadcom/bcm2710-rpi-3-b-plus broadcom/bcm2710-rpi-cm3"
@ -23,7 +22,6 @@ BR2_PACKAGE_RPI_FIRMWARE_VARIANT_PI=y
BR2_PACKAGE_RPI_FIRMWARE_CMDLINE_FILE="$(BR2_EXTERNAL_AIMEEOS_PATH)/board/raspberrypi3/cmdline.txt"
BR2_PACKAGE_RPI_FIRMWARE_CONFIG_FILE="$(BR2_EXTERNAL_AIMEEOS_PATH)/board/raspberrypi3/config.txt"
# BR2_TARGET_ROOTFS_TAR is not set
BR2_TARGET_GRUB2_BUILTIN_MODULES_EFI="boot configfile echo efi_gop fat gzio linux loadenv minicmd normal part_gpt probe regexp squash4 test zstd"
BR2_TARGET_UBOOT_BOARD_DEFCONFIG="rpi_3_32b"
BR2_PACKAGE_HOST_KMOD_XZ=y
AIMEEOS=y

View File

@ -1,13 +1,83 @@
ifeq ($(AIMEEOS),y)
BR2_TOOLCHAIN_BUILDROOT_VENDOR = "aimeeos"
# 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_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)
# Generate the initramfs image after building the target
BR2_ROOTFS_POST_BUILD_SCRIPT += $(BR2_EXTERNAL_AIMEEOS_PATH)/boot/mkinitramfs.sh
# Ensure the requisite GRUB2 modules are selected
define AIMEEOS_GRUB2_MODULES
boot
configfile
echo
efi_gop
fat
gzio
linux
loadenv
minicmd
normal
part_gpt
probe
regexp
squash4
terminfo
test
zstd
endef
BR2_TARGET_GRUB2_BUILTIN_MODULES_EFI += $(AIMEEOS_GRUB2_MODULES)
# Overwrite the grub.cfg provided by Buildroot with our own.
define AIMEEOS_GRUB2_INSTALL_IMAGES_CMDS
$(foreach tuple, $(GRUB2_TUPLES-y), \
$(INSTALL) -D -m 0644 $(BR2_EXTERNAL_AIMEEOS_PATH)/boot/grub2/grub.cfg $(GRUB2_CFG_$(tuple))
)
$(HOST_DIR)/bin/grub-editenv $(BINARIES_DIR)/efi-part/EFI/BOOT/grubenv set \
default=0 \
timeout=3 \
rootflags=$(AIMEEOS_DEFAULT_ROOTFLAGS)
endef
GRUB2_INSTALL_IMAGES_CMDS += $(AIMEEOS_GRUB2_INSTALL_IMAGES_CMDS)
ifneq ($(BR2_LINUX_KERNEL_IMAGE_TARGET_CUSTOM),)
AIMEEOS_KERNEL_FILENAME = $(BR2_LINUX_KERNEL_IMAGE_TARGET_CUSTOM)
else ifeq ($(BR2_LINUX_KERNEL_IMAGE),y)
AIMEEOS_KERNEL_FILENAME = Image
else ifeq ($(BR2_LINUX_KERNEL_IMAGEZ),y)
AIMEEOS_KERNEL_FILENAME = zImage
else ifeq ($(BR2_LINUX_KERNEL_VMLINUX),y)
AIMEEOS_KERNEL_FILENAME = vmlinux
else
$(error "Must define a Linux kernel target")
endif
# Generate the grub.cfg stub for the kernel embedded in rootfs.squashfs
define AIMEEOS_GEN_GRUB_CFG
$(BR2_EXTERNAL_AIMEEOS_PATH)/boot/grub2/gen-grub-cfg.sh
$(BR2_EXTERNAL_AIMEEOS_PATH)/boot/grub2/gen-grub-cfg.sh $(AIMEEOS_KERNEL_FILENAME)
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

@ -1,2 +0,0 @@
CONFIG_EFI=y
CONFIG_SQUASHFS=y

View File

@ -0,0 +1,10 @@
[Match]
Type=ether
Name=en* eth*
[Network]
DHCP=true
[DHCPv4]
ClientIdentifier=mac
UseDomain=true

View File

@ -0,0 +1,10 @@
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
select BR2_PACKAGE_WGET
select BR2_TARGET_GRUB2_INSTALL_TOOLS

View File

@ -0,0 +1,31 @@
################################################################################
#
# 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_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 \
$(TARGET_DIR)/usr/lib/systemd/system/var.mount
$(INSTALL) -D -m u=rw,go=r \
$(AIMEE_OS_UTILS_PKGDIR)/90-default.network \
$(TARGET_DIR)/usr/lib/systemd/network/90-default.network
endef
$(eval $(generic-package))

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

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

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