diff --git a/os/image/machine-id-setup.service b/os/image/machine-id-setup.service index 32c7798d9..77dd6f4eb 100644 --- a/os/image/machine-id-setup.service +++ b/os/image/machine-id-setup.service @@ -1,9 +1,9 @@ [Unit] -Description=Initialize shared machine-id on first boot +Description=Initialize shared machine-id when /data/ becomes available DefaultDependencies=no Before=sysinit.target After=data.mount -Requires=data.mount +ConditionPathExists=/data ConditionPathExists=!/data/machine-id [Service] diff --git a/os/image/make-image.js b/os/image/make-image.js new file mode 100644 index 000000000..4d2bfd3d5 --- /dev/null +++ b/os/image/make-image.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +import { $ } from "execa" + +const device = `/dev/nvme0n1` +const partn = 6 +const path = device + "p" + partn + +if (import.meta.main) { + if (process.getuid() !== 0) { + throw new Error("Please run as root.") + } + + // await $`mount ${path} /mnt` + // await $`fstrim /mnt` + // await $`umount ${path}` + + // Resize is not effective, it creates a smaller partition + // await $`e2fsck -f ${path}` + // await $`resize2fs ${path} 8M` + // await $`sgdisk --delete=${partn} --new=${partn}:0:+8M --typecode=${partn}:8300 -A ${partn}:set:0 -A ${partn}:set:1 -A ${partn}:set:62 -A ${partn}:set:63 --change-name=${partn}:DATA --partition-guid=${partn}:ce528120-d0dd-52be-aea3-8225fabd8a00 ${device}` + + // systemd-repart will recreate the partition on boot + await $`sgdisk --delete=${partn}` + // await $`partprobe ${device}` +} diff --git a/os/image/planktoscope.js b/os/image/planktoscope.js index 21988e6da..038b8365f 100644 --- a/os/image/planktoscope.js +++ b/os/image/planktoscope.js @@ -58,6 +58,7 @@ export async function updateMountpoints(device, rpios_partitions) { await setup_fstab(partitions) await setup_autoboot(partitions) await setup_machineid(partitions) + await setup_repart(partitions) } async function createPartitionTable(device) { @@ -78,23 +79,23 @@ async function createPartitionTable(device) { // BOOTLOADER partn++ - await $`sgdisk --new=${partn}:0:+8M --typecode=${partn}:0700 -A ${partn}:set:0 -A ${partn}:set:1 -A ${partn}:set:62 -A ${partn}:set:63 --change-name=${partn}:BOOTLOADER --partition-guid=${partn}:${stablePartUuid("BOOTLOADER")} ${device}` + await $`sgdisk --new=${partn}:0:+8M --typecode=${partn}:0700 --change-name=${partn}:BOOTLOADER --partition-guid=${partn}:${stablePartUuid("BOOTLOADER")} ${device}` // FIRMWARE X for (const bootname of bootnames) { partn++ - await $`sgdisk --new=${partn}:0:+256M --typecode=${partn}:0700 -A ${partn}:set:0 -A ${partn}:set:1 -A ${partn}:set:62 -A ${partn}:set:63 --change-name=${partn}:${"FIRMWARE_" + bootname} --partition-guid=${partn}:${stablePartUuid("FIRMWARE_" + bootname)} ${device}` + await $`sgdisk --new=${partn}:0:+256M --typecode=${partn}:0700 --change-name=${partn}:${"FIRMWARE_" + bootname} --partition-guid=${partn}:${stablePartUuid("FIRMWARE_" + bootname)} ${device}` } // ROOT X for (const bootname of bootnames) { partn++ - await $`sgdisk --new=${partn}:0:+10G --typecode=${partn}:8300 -A ${partn}:set:0 -A ${partn}:set:1 -A ${partn}:set:62 -A ${partn}:set:63 --change-name=${partn}:${"ROOT_" + bootname} --partition-guid=${partn}:${stablePartUuid("ROOT_" + bootname)} ${device}` + await $`sgdisk --new=${partn}:0:+10G --typecode=${partn}:8300 --change-name=${partn}:${"ROOT_" + bootname} --partition-guid=${partn}:${stablePartUuid("ROOT_" + bootname)} ${device}` } // "DATA" partn++ - await $`sgdisk --new=${partn}:0:0 --typecode=${partn}:8300 -A ${partn}:set:0 -A ${partn}:set:1 -A ${partn}:set:62 -A ${partn}:set:63 --change-name=${partn}:DATA --partition-guid=${partn}:${stablePartUuid("DATA")} ${device}` + await $`sgdisk --new=${partn}:0:0 --typecode=${partn}:8300 --change-name=${partn}:DATA --partition-guid=${partn}:${stablePartUuid("DATA")} ${device}` await $`sgdisk --verify ${device}` @@ -163,10 +164,6 @@ async function create_datafs(device, rootfs) { await $`wipefs -a ${path}` await $`mkfs.ext4 -q ${path}` - // will be grown by x-systemd.growfs, see fstab - await $`resize2fs -M ${path}` - await $`e2fsck -f -p ${path}` - await $`mount ${path} ${mountpoint}` // /data/home @@ -276,6 +273,7 @@ async function setup_config(rpios_partitions, partitions) { "utf8", ) + // See https://www.raspberrypi.com/documentation/computers/config_txt.html#boot_partition-2 let config = "[all]\n\n" for (const bootname of bootnames) { const part = partitions[`FIRMWARE_${bootname}`] @@ -355,7 +353,7 @@ async function setup_fstab(partitions) { const datafs_partuuid = partitions[`DATA`].partuuid const fstab = dedent` PARTUUID=${bootloader_partuuid} /bootloader vfat defaults,noatime,ro 0 2 - PARTUUID=${datafs_partuuid} /data ext4 defaults,noatime,x-systemd.growfs 0 2 + PARTUUID=${datafs_partuuid} /data ext4 defaults,noatime 0 2 /data/home /home none bind 0 0 ` // TODO: when we go readonly @@ -371,6 +369,28 @@ async function setup_fstab(partitions) { } } +// repart will create the DATA GPT partition but won't grow the EXT4 filesystem +// we use x-systemd.growfs in fstab for that +// > Note that these definitions may only be used to create and initialize new partitions or to grow existing ones. In the latter case, it will not grow the contained files systems however; separate mechanisms, such as systemd-growfs(8) may be used to grow the file systems inside of these partitions. +// https://www.freedesktop.org/software/systemd/man/latest/repart.d.html#Description +async function setup_repart(partitions) { + const datafs_partuuid = partitions[`DATA`].partuuid + const conf = dedent` + [Partition] + UUID=${datafs_partuuid} + GrowFileSystem=yes + Type=linux-generic + ` + for (const bootname of bootnames) { + const path = join( + partitions[`ROOT_${bootname}`].mountpoint, + "usr/lib/repart.d", + ) + await mkdir(path, { recursive: true }) + await writeFile(join(path, "50-data.conf"), conf) + } +} + export async function getPartitions(device) { const devices = await getBlockDevices(device) const partitions = Object.create(null) diff --git a/os/image/repart.d/10-bootloader.conf b/os/image/repart.d/10-bootloader.conf new file mode 100644 index 000000000..f765152d8 --- /dev/null +++ b/os/image/repart.d/10-bootloader.conf @@ -0,0 +1,5 @@ +[Partition] +Label=BOOTLOADER +Type=ebd0a0a2-b9e5-4433-87c0-68b6b72699c7 +SizeMinBytes=8M +SizeMaxBytes=8M diff --git a/os/image/repart.d/20-firmware_a.conf b/os/image/repart.d/20-firmware_a.conf new file mode 100644 index 000000000..55b39b674 --- /dev/null +++ b/os/image/repart.d/20-firmware_a.conf @@ -0,0 +1,5 @@ +[Partition] +Label=FIRMWARE_A +Type=ebd0a0a2-b9e5-4433-87c0-68b6b72699c7 +SizeMinBytes=256M +SizeMaxBytes=256M diff --git a/os/image/repart.d/30-firmware_b.conf b/os/image/repart.d/30-firmware_b.conf new file mode 100644 index 000000000..9e2308a4f --- /dev/null +++ b/os/image/repart.d/30-firmware_b.conf @@ -0,0 +1,5 @@ +[Partition] +Label=FIRMWARE_B +Type=ebd0a0a2-b9e5-4433-87c0-68b6b72699c7 +SizeMinBytes=256M +SizeMaxBytes=256M diff --git a/os/image/repart.d/40-root_a.conf b/os/image/repart.d/40-root_a.conf new file mode 100644 index 000000000..1a7979c3d --- /dev/null +++ b/os/image/repart.d/40-root_a.conf @@ -0,0 +1,5 @@ +[Partition] +Label=ROOT_A +Type=linux-generic +SizeMinBytes=10G +SizeMaxBytes=10G diff --git a/os/image/repart.d/50-root_b.conf b/os/image/repart.d/50-root_b.conf new file mode 100644 index 000000000..b93d6eb91 --- /dev/null +++ b/os/image/repart.d/50-root_b.conf @@ -0,0 +1,5 @@ +[Partition] +Label=ROOT_B +Type=linux-generic +SizeMinBytes=10G +SizeMaxBytes=10G diff --git a/os/image/repart.d/60-data.conf b/os/image/repart.d/60-data.conf new file mode 100644 index 000000000..95505edc8 --- /dev/null +++ b/os/image/repart.d/60-data.conf @@ -0,0 +1,5 @@ +[Partition] +Label=DATA +UUID=ce528120-d0dd-52be-aea3-8225fabd8a00 +Type=linux-generic +GrowFileSystem=yes diff --git a/os/rauc/README.md b/os/rauc/README.md index 31e96949f..a76d9a761 100644 --- a/os/rauc/README.md +++ b/os/rauc/README.md @@ -15,6 +15,14 @@ sudo ./rauc.js create-bundle /dev/device B [version] This will create a bundle from partitions `FIRMWARE_B` and `ROOT_B` on device `/dev/device`. +A bundle can be installed to either slot. So please consider: + +* Files on `FIRMWARE_A` and `FIRMWARE_B` should be considered identical. +* Files on `ROOT_A` and `ROOT_B` should be considered identical. +* A bundle must work for any slot + +We use the Raspberry Pi firmware and bootloader to dynamically switch between A and B. + ## Install a bundle ```sh