12 Commits

Author SHA1 Message Date
8e49163f6e ci: Update workspace volume PVC name
All checks were successful
Aimee OS/aimee-os/pipeline/head This commit looks good
Moving to using a single PVC for all Buildroot jobs.
2025-11-15 13:13:48 -06:00
91b584dfd2 utils: Add option to run root shell on console
Some checks reported errors
Aimee OS/aimee-os/pipeline/head Something is wrong with the build of this commit
Until I develop a captive portal-based mechanism for initially
configuring the WiFi on Raspberry Pi machines, the easiest way to do
initial provisioning is using the CLI via the serial console.  Since the
root account has no password, and I don't want to have to specify one in
downstream projects' configurations, I've added a configuration option
to enable automatically launching a root shell connected to the serial
console instead of a login prompt.  The security risks here are pretty
minimal, because someone would need phyiscal access to the device in
order to use this shell, in which case they could use any number of
other methods to get control of the system.
2025-11-15 13:05:37 -06:00
b2a0aab680 Include sleep command in initramfs
All checks were successful
Aimee OS/aimee-os/pipeline/head This commit looks good
This is needed in case we need to poll for the block device containing
the root filesystem.
2025-09-03 09:51:51 -05:00
d9415b0fb5 Set machine-id to uninitialized
An empty `/etc/machine-id` file does _not_ trigger systemd's "first
boot" logic.  This means with `ConditionFirstBoot=true` will not run,
and the unit preset policy will not be applied.  To ensure a first boot
behaves the way we want, we need to pre-populate the `/etc/machine-id`
file with the string `uninitialized\n`, per _machine-id(5)_ § _First
Boot Semantics_.
2025-09-03 09:48:20 -05:00
f838e9f0bd Do not run systemd preset-all at build time
All checks were successful
Aimee OS/aimee-os/pipeline/head This commit looks good
Let systemd apply preset policy on first boot.  This keeps symlinks out
of the immutable image.
2025-09-03 08:30:19 -05:00
62035f3223 init-storage: Add retry loop for findfs
All checks were successful
Aimee OS/aimee-os/pipeline/head This commit looks good
Occasionally, especially on a Raspberry Pi, the storage subsystem has
not fully enumerated all of the disks before the `init-storage` script
starts.  This prevents the system from being able to boot, since the
script attempts to identify the root filesystem very early.  Now, we try
a few times to identify the filesystem before giving up, to give the
kernel a chance to discover everything.
2025-09-03 08:06:26 -05:00
f3c6c71bfa Force built-in kernel overlayfs driver
All checks were successful
Aimee OS/aimee-os/pipeline/head This commit looks good
As with the other "real" filesystems, we need the OverlayFS driver to be
built into the kernel so it is available in early boot.
2025-08-31 08:09:18 -05:00
7d44d03279 Force built-in kernel support for filesystems
All checks were successful
Aimee OS/aimee-os/pipeline/head This commit looks good
the `KCONFIG_ENABLE_OPT` macro will not switch a `=m` value to `=y`, so
if the downstream kernel configuration already enables these
filesystems as modules, they will not get built into the kernel image,
making the system unbootable.  We need to call `KCONFIG_SET_OPT` instead
to explicitly set them to be built-in.
2025-08-30 15:36:07 -05:00
9d00ebfdba Use LINUX_IMAGE_NAME to find kernel file
All checks were successful
Aimee OS/aimee-os/pipeline/head This commit looks good
It turns out, Buildroot already sets a variable, `LINUX_IMAGE_NAME`,
that holds the name of the Linux kernel image file.  Instead of figuring
it out for ourselves (incorrectly, it turns out), we can just use that.
2025-08-30 14:04:53 -05:00
dd6261aa33 Configure network interfaces by default
All checks were successful
Aimee OS/aimee-os/pipeline/head This commit looks good
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
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
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
14 changed files with 398 additions and 24 deletions

View File

@@ -28,6 +28,16 @@ copy_vol() {
umount "${tmpdir}"
}
find_part() {
_i=0
while [ $_i -lt 5 ]; do
findfs "$1" 2>/dev/null && return
_i=$((_i + 1))
sleep 1
done
findfs "$1"
}
format_dev() {
dev="$1"
partno=$(partition_number "${dev}")
@@ -140,8 +150,8 @@ setup_etc() {
/sysroot/etc
}
rootdev=$(findfs "$1")
datapart=$(findfs "${2:-PARTLABEL=aimeeos-data}")
rootdev=$(find_part "$1")
datapart=$(find_part "${2:-PARTLABEL=aimeeos-data}")
if [ -b "${datapart}" ]; then
printf 'Found data partition: %s\n' "${datapart}" >&2
else

View File

@@ -83,6 +83,7 @@ bin_install \
/bin/mount \
/bin/rm \
/bin/sh \
/bin/sleep \
/bin/sort \
/bin/tail \
/bin/umount \

3
ci/Jenkinsfile vendored
View File

@@ -13,7 +13,7 @@ pipeline {
yamlFile 'ci/podTemplate.yaml'
yamlMergeStrategy merge()
workspaceVolume persistentVolumeClaimWorkspaceVolume(
claimName: 'buildroot-aimeeos'
claimName: 'buildroot'
)
defaultContainer 'build'
}
@@ -65,6 +65,7 @@ pipeline {
'firmware.img.zst',
'rootfs.squashfs',
'sdcard.img.zst',
'update.tar.zst',
].join(','))
}
}

View File

@@ -7,10 +7,8 @@ BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_AIMEEOS_PATH)/board/qemu/post-image
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_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_AIMEEOS_PATH)/kernel/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"
BR2_TARGET_GRUB2_BUILTIN_MODULES_EFI="boot configfile echo efi_gop fat gzio linux loadenv minicmd normal part_gpt probe regexp squash4 terminfo test zstd"
BR2_PACKAGE_AIMEE_OS_ROOT_SHELL=y

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 terminfo test zstd"
BR2_TARGET_UBOOT_BOARD_DEFCONFIG="rpi_3_32b"
BR2_PACKAGE_HOST_KMOD_XZ=y
AIMEEOS=y

View File

@@ -1,20 +1,54 @@
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 =
# Do not run preset-all at build time
SYSTEMD_PRESET_ALL =
# 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)
$(call KCONFIG_ENABLE_OPT,CONFIG_EFI)
$(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_VFAT_FS)
$(call KCONFIG_SET_OPT,CONFIG_BTRFS_FS,y)
$(call KCONFIG_SET_OPT,CONFIG_OVERLAY_FS,y)
$(call KCONFIG_SET_OPT,CONFIG_SQUASHFS,y)
$(call KCONFIG_SET_OPT,CONFIG_MSDOS_FS,y)
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), \
@@ -27,24 +61,19 @@ $(HOST_DIR)/bin/grub-editenv $(BINARIES_DIR)/efi-part/EFI/BOOT/grubenv set \
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 $(AIMEEOS_KERNEL_FILENAME)
$(BR2_EXTERNAL_AIMEEOS_PATH)/boot/grub2/gen-grub-cfg.sh $(LINUX_IMAGE_NAME)
endef
LINUX_TARGET_FINALIZE_HOOKS += AIMEEOS_GEN_GRUB_CFG
define AIMEEOS_TARGET_FINALIZE_HOOKS
echo uninitialized > $(TARGET_DIR)/etc/machine-id
endef
TARGET_FINALIZE_HOOKS += AIMEEOS_TARGET_FINALIZE_HOOKS
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

@@ -6,3 +6,27 @@ 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
if BR2_PACKAGE_AIMEE_OS_UTILS
config AIMEE_OS_ROOT_SHELL
bool "Spawn a root shell on the serial console by default"
help
With this option enabled, the system will boot up normally, with
a root shell connected to the default serial console. This is
useful for initial provisioning and troubleshooting.
if AIMEE_OS_ROOT_SHELL
config AIMEE_OS_ROOT_SHELL_CONSOLE
string "Console name"
default ttyAMA0
help
Name of the console device where the root shell will be spawned.
Do not include the /dev prefix
endif
endif

View File

@@ -12,10 +12,36 @@ 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 \
$(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
define AIMEE_OS_UTILS_ROOT_SHELL_INSTALL
$(INSTALL) -D -m u=rw,go=r \
$(AIMEE_OS_UTILS_PKGDIR)/root-shell@.service \
$(TARGET_DIR)/usr/lib/systemd/system/root-shell@.service
$(INSTALL) -d -m u=rwx,go=rx \
$(TARGET_DIR)/usr/lib/systemd/system-preset
printf 'enable root-shell@.service %s\n' \
$(AIMEE_OS_ROOT_SHELL_CONSOLE) \
> $(TARGET_DIR)/usr/lib/systemd/system-preset/50-root-shell.preset
endef
ifeq ($(AIMEE_OS_ROOT_SHELL),y)
AIMEE_OS_UTILS_INSTALL_INIT_SYSTEMD += $(AIMEE_OS_UTILS_ROOT_SHELL_INSTALL)
endif
$(eval $(generic-package))

View File

@@ -0,0 +1,26 @@
[Unit]
Description=root shell on %I
After=sshd.service
Conflicts=shutdown.target
Conflicts=getty@%i.service serial-getty@%i.service
[Service]
Type=idle
Environment=TERM=linux
ExecStart=/bin/sh
Restart=always
RestartSec=0
StandardInput=tty
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
KillMode=process
IgnoreSIGPIPE=no
KillSignal=SIGHUP
# Unset locale for the console getty since the console has problems
# displaying some internationalized messages.
UnsetEnvironment=LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION
[Install]
WantedBy=multi-user.target

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