Skip to content

Commit d93e455

Browse files
committed
Add btrfs subvolume scheme, boot entries and tooling
Define the subvolume layout (@, @home, @var-log, @var-cache, @snapshots, boot) with per-subvolume mount options, keep /boot uncompressed so U-Boot can read the kernel/dtb/initrd, and factor out root device retrieval into a helper. Generate the BLS boot entries, deriving the devicetreedir/overlay prefix from each entry's root subvolume so U-Boot resolves the in-entry paths correctly, with FDT_SUBVOL_PREFIX as an override. Install btrfs-progs and btrfs-compsize.
1 parent 1ca254d commit d93e455

3 files changed

Lines changed: 121 additions & 23 deletions

File tree

debian-rk3576-img.yaml

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
{{ $sectorsize := or .sectorsize 512 }}
2-
{{ $imagesize := or .imagesize "4GiB" }}
3-
{{ $kerneldir := or .kerneldir "prebuilt/linux" }}
4-
{{ $cmdline := or .cmdline "audit=0 console=tty1 console=ttyS0,1500000n8" }}
2+
{{ $imagesize := or .imagesize "4GiB" }}
3+
{{ $kerneldir := or .kerneldir "prebuilt/linux" }}
4+
{{ $cmdline := or .cmdline "audit=0 console=tty1 console=ttyS0,1500000n8" }}
5+
{{ $fslabel := or .fslabel "flipperos" }}
6+
{{- /* fs-wide btrfs mount options. ssd is FORCED (loopback/eMMC isn't reliably
7+
detected as non-rotational). */ -}}
8+
{{ $btrfsopts := or .btrfsopts "compress=zstd,noatime,ssd,discard=async" }}
9+
{{ $rootdev := "findmnt -fn --nofsroot -o SOURCE --target $IMAGEMNTDIR" }}
510

611
architecture: arm64
712
sectorsize: {{ $sectorsize }}
8-
913
actions:
1014
- action: unpack
1115
description: Load a tarball of the root filesystem
1216
file: debian-ospack.tar.gz
1317

18+
# debos formats btrfs (default flags, random FSUUID), mounts the top level at
19+
# $IMAGEMNTDIR, and records the standard root fstab line + root=UUID.
20+
# fsck:false -> passno 0 (no boot-time fsck for btrfs).
1421
- action: image-partition
1522
imagename: debian-nobootloader.img
1623
imagesize: {{ $imagesize }}
@@ -22,20 +29,74 @@ actions:
2229
end: 64MiB
2330
- name: root
2431
fs: btrfs
32+
fslabel: {{ $fslabel }}
33+
fsck: false
2534
start: 64MiB
2635
end: -1MiB
2736
mountpoints:
2837
- mountpoint: /
2938
partition: root
30-
options: [ compress=zstd, x-systemd.growfs ]
39+
options: [ compress=zstd, noatime, ssd, discard=async, x-systemd.growfs ]
3140

41+
# Subvolume layout (no reformat). /boot is a TOP-LEVEL subvolume named "boot"
42+
# (not "@boot") so it sits outside @ -> a single shared /boot that root snapshots
43+
# never capture, and so U-Boot, which reads the default (top-level) subvolume,
44+
# resolves /boot/extlinux/... straight into it. Keep the default subvolume as the
45+
# top level (do NOT set-default @) or U-Boot won't see this sibling /boot.
3246
- action: run
33-
description: Enable zstd compression on the image mount
47+
description: Create subvolumes and mount the layout
3448
chroot: false
35-
command: mount -o remount,compress=zstd "$IMAGEMNTDIR"
49+
command: |
50+
set -eu
51+
ROOTPART="$({{ $rootdev }})"
52+
53+
# debos has the top level (subvolid=5) mounted here; create subvolumes on it.
54+
for sv in @ boot @home @snapshots @var-log @var-cache; do
55+
btrfs subvolume create "$IMAGEMNTDIR/$sv"
56+
done
57+
umount "$IMAGEMNTDIR"
58+
59+
# Mount @ at the mount point debos owns, then nest the rest under it.
60+
mount -t btrfs -o "{{ $btrfsopts }},subvol=@" "$ROOTPART" "$IMAGEMNTDIR"
61+
mkdir -p "$IMAGEMNTDIR/boot" \
62+
"$IMAGEMNTDIR/home" \
63+
"$IMAGEMNTDIR/.snapshots" \
64+
"$IMAGEMNTDIR/var/log" \
65+
"$IMAGEMNTDIR/var/cache"
66+
mount -t btrfs -o "{{ $btrfsopts }},subvol=boot" "$ROOTPART" "$IMAGEMNTDIR/boot"
67+
# Keep /boot uncompressed so U-Boot can read the kernel/dtb/initrd regardless
68+
# of whether its btrfs driver was built with zstd support. The property is
69+
# stored on the subvolume and inherited by files added later (kernel updates).
70+
btrfs property set "$IMAGEMNTDIR/boot" compression none
71+
mount -t btrfs -o "{{ $btrfsopts }},subvol=@home" "$ROOTPART" "$IMAGEMNTDIR/home"
72+
# @snapshots is mounted only for the build (to write the Factory Image); it's
73+
# not in fstab, so the .snapshots mountpoint is removed from @ before detach.
74+
mount -t btrfs -o "{{ $btrfsopts }},subvol=@snapshots" "$ROOTPART" "$IMAGEMNTDIR/.snapshots"
75+
mount -t btrfs -o "{{ $btrfsopts }},subvol=@var-log" "$ROOTPART" "$IMAGEMNTDIR/var/log"
76+
mount -t btrfs -o "{{ $btrfsopts }},subvol=@var-cache" "$ROOTPART" "$IMAGEMNTDIR/var/cache"
3677
78+
# debos writes the standard root line into /etc/fstab and root=UUID into the
79+
# cmdline; the root subvolume is selected by the appended rootflags=subvol=@.
3780
- action: filesystem-deploy
38-
append-kernel-cmdline: {{ $cmdline }}
81+
append-kernel-cmdline: rootflags=subvol=@ {{ $cmdline }}
82+
83+
# Add fstab entries for the non-root subvolumes (debos only generated the root
84+
# line). The FSUUID is read back at build time. @snapshots is intentionally
85+
# omitted -- it holds the Factory Image and is mounted on demand for rollback,
86+
# never auto-mounted at runtime.
87+
- action: run
88+
description: Append subvolume entries to /etc/fstab
89+
chroot: false
90+
command: |
91+
set -eu
92+
ROOTPART="$({{ $rootdev }})"
93+
FSUUID="$(blkid -o value -s UUID -p -c none "$ROOTPART")"
94+
cat >> "$IMAGEMNTDIR/etc/fstab" <<EOF
95+
UUID=$FSUUID /boot btrfs {{ $btrfsopts }},subvol=boot 0 0
96+
UUID=$FSUUID /home btrfs {{ $btrfsopts }},subvol=@home 0 0
97+
UUID=$FSUUID /var/log btrfs {{ $btrfsopts }},subvol=@var-log 0 0
98+
UUID=$FSUUID /var/cache btrfs {{ $btrfsopts }},subvol=@var-cache 0 0
99+
EOF
39100
40101
- action: overlay
41102
description: Copy Linux kernel packages into the target
@@ -47,4 +108,18 @@ actions:
47108
description: Install Linux kernel packages inside the target
48109
chroot: true
49110
command: find /var/cache/linux-kernel -name linux-image-\*.deb -a ! -name \*-dbg_\* -print0 | xargs -0 apt install -y
50-
111+
112+
# Unmount the nested subvolumes (deepest first), leaving @ mounted at
113+
# $IMAGEMNTDIR. debos' image-partition Cleanup unmounts @ and detaches the loop;
114+
# without this it would fail with EBUSY on the still-mounted children.
115+
- action: run
116+
description: Unmount nested subvolumes before debos detaches the loop
117+
chroot: false
118+
command: |
119+
set -eu
120+
sync
121+
findmnt -rno TARGET --submounts "$IMAGEMNTDIR" \
122+
| awk -v root="$IMAGEMNTDIR" '$0 != root' \
123+
| awk '{ print length, $0 }' | sort -rn | cut -d" " -f2- \
124+
| while read -r mp; do umount "$mp"; done
125+
rmdir "$IMAGEMNTDIR/.snapshots"

debian-rk3576-ospack.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ actions:
3333
- breeze-gtk-theme
3434
- bsdextrautils
3535
- bsdmainutils
36+
- btrfs-compsize
37+
- btrfs-progs
3638
- build-essential
3739
- cage
3840
- ca-certificates
@@ -43,6 +45,7 @@ actions:
4345
- dmidecode
4446
- dnsmasq-base
4547
- drm-info
48+
- duperemove
4649
- gstreamer1.0-plugins-bad
4750
- gstreamer1.0-tools
4851
- e2fsprogs

overlays/configs/kernel/install.d/90-loaderentry.install

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ TITLE_MAX="${FLIPPER_TITLE_MAX:-26}"
3535
DTB_DIR="/usr/lib/linux-image-$KERNEL_VERSION"
3636
DTB_DIRS="$DTB_DIR /usr/lib/modules/$KERNEL_VERSION/dtb"
3737

38+
# U-Boot reads the btrfs TOP LEVEL (subvolid 5), so in-entry devicetreedir/overlay paths must
39+
# be prefixed with the root subvolume the entry boots -- /@<subvol>/usr/lib/... -- where that
40+
# root's DTBs live (the kernel package installs them in every root's /usr/lib). The prefix is
41+
# derived per entry from its own rootflags=subvol= (each profile boots a different root). The
42+
# staged kernel/initrd live under /boot (the boot subvolume) and are never prefixed.
43+
# Set FDT_SUBVOL_PREFIX to override the derivation (e.g. "" if the root is not a subvolume).
44+
fdt_prefix() { # $1 = options string -> "/<rootflags-subvol>" (or $FDT_SUBVOL_PREFIX if set)
45+
if [ "${FDT_SUBVOL_PREFIX+x}" = x ]; then printf '%s' "$FDT_SUBVOL_PREFIX"; return 0; fi
46+
_sv=$(printf '%s' "$1" | tr ' ' '\n' | sed -n 's/^rootflags=subvol=//p' | tail -n1)
47+
[ -n "$_sv" ] && printf '/%s' "$_sv"
48+
return 0
49+
}
50+
3851
log() { [ "${KERNEL_INSTALL_VERBOSE:-0}" -gt 0 ] && echo "flipper-bls: $*" >&2; return 0; }
3952
die() { echo "flipper-bls: $*" >&2; exit 1; }
4053
trim() { printf '%s' "$1" | sed 's/^ *//; s/ *$//'; }
@@ -53,8 +66,12 @@ if [ "$COMMAND" = "remove" ]; then
5366
fi
5467
[ "$COMMAND" = "add" ] || exit 0
5568

56-
# --- locate the boot mount, so in-entry paths are relative to the boot partition --
57-
[ -n "$BOOT_MNT" ] || BOOT_MNT="$(stat -c %m "$BOOT_ROOT")"
69+
# In-entry paths are relative to the partition U-Boot actually reads. On Flipper One
70+
# that is always the root partition — /boot is a directory/subvolume on it, never a
71+
# separate boot partition U-Boot mounts — so paths must keep the /boot prefix, i.e.
72+
# BOOT_MNT=/. (Override BOOT_MNT in the environment if /boot ever becomes its own
73+
# partition, which would make boot_rel strip the mountpoint again.)
74+
[ -n "$BOOT_MNT" ] || BOOT_MNT="/"
5875

5976
# absolute path under BOOT_ROOT -> path as it must appear inside a BLS entry (relative
6077
# to the boot partition; identical to the absolute path when /boot is on the rootfs).
@@ -130,7 +147,7 @@ fi
130147
# devicetreedir (U-Boot fdtdir extension) — referenced in place, opt-in via a
131148
# /etc/kernel/devicetreedir file containing a true boolean. Only when no single
132149
# devicetree was configured.
133-
DEVICETREEDIR_SRC=""; DEVICETREEDIR_ENTRY=""
150+
DEVICETREEDIR_SRC=""; DEVICETREEDIR_REL=""
134151
if [ -z "$DEVICETREE" ] && [ -f "$CONF_ROOT/devicetreedir" ]; then
135152
_v=""; read -r _v <"$CONF_ROOT/devicetreedir" || :
136153
case "$_v" in
@@ -143,7 +160,7 @@ if [ -z "$DEVICETREE" ] && [ -f "$CONF_ROOT/devicetreedir" ]; then
143160
[ -n "$DEVICETREEDIR_SRC" ] && break
144161
done
145162
[ -n "$DEVICETREEDIR_SRC" ] || die "no device-tree directory for $KERNEL_VERSION"
146-
DEVICETREEDIR_ENTRY="$(boot_rel "$DEVICETREEDIR_SRC")"
163+
DEVICETREEDIR_REL="$(boot_rel "$DEVICETREEDIR_SRC")" # /usr/lib/...; prefixed per entry
147164
;;
148165
esac
149166
fi
@@ -180,6 +197,8 @@ done
180197
# write_entry FILE TITLE OPTIONS FDTOVERLAYS
181198
write_entry() {
182199
_f="$1"; _title="$2"; _opts="$3"; _fdtov="$4"
200+
# devicetreedir is prefixed with THIS entry's root subvol (U-Boot reads subvolid 5)
201+
_dtdir=""; [ -n "$DEVICETREEDIR_REL" ] && _dtdir="$(fdt_prefix "$_opts")$DEVICETREEDIR_REL"
183202
{
184203
echo "# Boot Loader Specification type#1 entry (Flipper One)"
185204
echo "title $_title"
@@ -188,21 +207,22 @@ write_entry() {
188207
[ -n "$SORT_KEY" ] && echo "sort-key $SORT_KEY"
189208
[ -n "$_opts" ] && echo "options $_opts"
190209
echo "linux $KERNEL_ENTRY"
191-
[ -n "$DEVICETREE_ENTRY" ] && echo "devicetree $DEVICETREE_ENTRY"
192-
[ -n "$DEVICETREEDIR_ENTRY" ] && echo "devicetreedir $DEVICETREEDIR_ENTRY"
193-
[ -n "$_fdtov" ] && echo "devicetree-overlay $_fdtov"
210+
[ -n "$DEVICETREE_ENTRY" ] && echo "devicetree $DEVICETREE_ENTRY"
211+
[ -n "$_dtdir" ] && echo "devicetreedir $_dtdir"
212+
[ -n "$_fdtov" ] && echo "devicetree-overlay $_fdtov"
194213
for _i in $INITRD_ENTRIES; do echo "initrd $_i"; done
195214
} >"$_f"
196215
return 0 # don't let a trailing empty conditional's status abort `set -e`
197216
}
198217

199218
# Echo the space-separated paths of the named DT overlays (flat, next to the DTBs).
200219
# Returns non-zero with no output if any one is missing.
201-
overlay_paths() {
220+
overlay_paths() { # $1 = subvol prefix (e.g. /@Desktop); remaining args = overlay names
221+
_pfx="$1"; shift
202222
_paths=""
203223
for _n in "$@"; do
204-
[ -f "$OVERLAY_DIR/$_n.dtbo" ] || return 1
205-
_paths="$_paths${_paths:+ }$OVERLAY_DIR/$_n.dtbo"
224+
[ -f "$OVERLAY_DIR/$_n.dtbo" ] || return 1 # check the real on-disk path
225+
_paths="$_paths${_paths:+ }$_pfx$OVERLAY_DIR/$_n.dtbo"
206226
done
207227
printf '%s' "$_paths"
208228
}
@@ -225,10 +245,10 @@ make_title() {
225245
fi
226246
}
227247

228-
# Base entry == the [DEFAULT] profile.
229-
write_entry "$ENTRY_STEM.conf" "$(make_title '[DEFAULT]')" "$BASE_OPTS" ""
248+
# Base entry == the [DESKTOP] profile.
249+
write_entry "$ENTRY_STEM.conf" "$(make_title '[DESKTOP]')" "$BASE_OPTS" ""
230250

231-
[ -f "$PROFILES" ] || { log "$PROFILES not found, only the [DEFAULT] entry written"; exit 0; }
251+
[ -f "$PROFILES" ] || { log "$PROFILES not found, only the [DESKTOP] entry written"; exit 0; }
232252

233253
while IFS='|' read -r tok suf extra gate dtbos legend; do
234254
tok="$(echo "$tok" | tr -d '[:space:]')"
@@ -257,7 +277,7 @@ while IFS='|' read -r tok suf extra gate dtbos legend; do
257277
required=0
258278
case "$dtbos" in '!'*) required=1; dtbos="$(trim "${dtbos#\!}")" ;; esac
259279

260-
paths="$(overlay_paths $dtbos)" || paths=""
280+
paths="$(overlay_paths "$(fdt_prefix "$opts")" $dtbos)" || paths=""
261281
if [ -z "$paths" ] && [ "$required" = 1 ]; then
262282
log "skip $tok (overlays not found for $KERNEL_VERSION)"
263283
continue

0 commit comments

Comments
 (0)