Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ RUN apt-get install -y \
flex \
parted \
fdisk \
btrfs-progs \
python3-dev \
python3-libfdt \
python3-setuptools \
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ sudo apt install git build-essential crossbuild-essential-arm64 bc bison flex \
For assembling disk images — debos, bmaptool, and zeekstd all need to be installed from source:

```bash
sudo apt install golang pipx pigz cargo parted fdisk mmdebstrap \
sudo apt install golang pipx pigz cargo parted fdisk btrfs-progs mmdebstrap \
systemd-resolved systemd-container qemu-user-binfmt \
libglib2.0-dev libostree-dev fakemachine

Expand Down
10 changes: 0 additions & 10 deletions build-images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,10 @@ trap cleanup EXIT

bmaptool copy "$IMG_OUT"/debian-rootfs.img.zst "$IMG_OUT"/debian-rootfs.img
sync "$IMG_OUT"/debian-rootfs.img
TMP=`mktemp`
chmod 644 "$TMP"

# Boot entries are pure BLS in /boot/loader/entries, read directly by U-Boot's
# 'bls' bootmeth (no extlinux.conf). Nothing to rewrite here for the boot menu.

debugfs -R "cat /usr/lib/os-release" "$IMG_OUT"/debian-rootfs.img > "$TMP"
echo "BUILD_ID=$BUILD_ID" >> "$TMP"
debugfs -w -R "rm /usr/lib/os-release" "$IMG_OUT"/debian-rootfs.img
debugfs -w -R "write $TMP /usr/lib/os-release" "$IMG_OUT"/debian-rootfs.img
sync "$IMG_OUT"/debian-rootfs.img

rm -f "$TMP"

for s in 512 4096; do
echo "Creating images for $s-byte sector size"
truncate -s "$IMGSIZE" "$TMPDIR"/debian-"$s"-nobootloader-"$BUILD_ID".img
Expand Down
115 changes: 107 additions & 8 deletions debian-rk3576-img.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
{{ $sectorsize := or .sectorsize 512 }}
{{ $imagesize := or .imagesize "4GiB" }}
{{ $kerneldir := or .kerneldir "prebuilt/linux" }}
{{ $cmdline := or .cmdline "audit=0 console=tty1 console=ttyS0,1500000n8" }}
{{ $imagesize := or .imagesize "4GiB" }}
{{ $kerneldir := or .kerneldir "prebuilt/linux" }}
{{ $cmdline := or .cmdline "audit=0 console=tty1 console=ttyS0,1500000n8" }}
{{ $fslabel := or .fslabel "flipperos" }}
{{- /* fs-wide btrfs mount options. ssd is FORCED (loopback/eMMC isn't reliably
detected as non-rotational). */ -}}
{{ $btrfsopts := or .btrfsopts "compress=zstd,noatime,ssd,discard=async" }}
{{ $rootdev := "findmnt -fn --nofsroot -o SOURCE --target $IMAGEMNTDIR" }}

architecture: arm64
sectorsize: {{ $sectorsize }}

actions:
- action: unpack
description: Load a tarball of the root filesystem
file: debian-ospack.tar.gz

# debos formats btrfs (default flags, random FSUUID), mounts the top level at
# $IMAGEMNTDIR, and records the standard root fstab line + root=UUID.
# fsck:false -> passno 0 (no boot-time fsck for btrfs).
- action: image-partition
imagename: debian-nobootloader.img
imagesize: {{ $imagesize }}
Expand All @@ -21,17 +28,86 @@ actions:
start: 32KiB
end: 64MiB
- name: root
fs: ext4
fs: btrfs
fslabel: {{ $fslabel }}
fsck: false
start: 64MiB
end: -1MiB
flags: [ boot ]
mountpoints:
- mountpoint: /
partition: root
options: [ x-systemd.growfs ]
options: [ compress=zstd, noatime, ssd, discard=async, x-systemd.growfs ]

# Subvolume layout (no reformat). /boot is a TOP-LEVEL subvolume named "boot"
# (not "@boot") so it sits outside @ -> a single shared /boot that root snapshots
# never capture, and so U-Boot, which reads the default (top-level) subvolume,
# resolves /boot/extlinux/... straight into it. Keep the default subvolume as the
# top level (do NOT set-default @) or U-Boot won't see this sibling /boot.
- action: run
description: Create subvolumes and mount the layout
chroot: false
command: |
set -eu
ROOTPART="$({{ $rootdev }})"

# debos has the top level (subvolid=5) mounted here; create subvolumes on it.
for sv in @ boot @home @snapshots @var-log @var-cache; do
btrfs subvolume create "$IMAGEMNTDIR/$sv"
done
umount "$IMAGEMNTDIR"

# Mount @ at the mount point debos owns, then nest the rest under it.
mount -t btrfs -o "{{ $btrfsopts }},subvol=@" "$ROOTPART" "$IMAGEMNTDIR"
mkdir -p "$IMAGEMNTDIR/boot" \
"$IMAGEMNTDIR/home" \
"$IMAGEMNTDIR/.snapshots" \
"$IMAGEMNTDIR/var/log" \
"$IMAGEMNTDIR/var/cache"
mount -t btrfs -o "{{ $btrfsopts }},subvol=boot" "$ROOTPART" "$IMAGEMNTDIR/boot"
# Keep /boot uncompressed so U-Boot can read the kernel/dtb/initrd regardless
# of whether its btrfs driver was built with zstd support. The property is
# stored on the subvolume and inherited by files added later (kernel updates).
btrfs property set "$IMAGEMNTDIR/boot" compression none
mount -t btrfs -o "{{ $btrfsopts }},subvol=@home" "$ROOTPART" "$IMAGEMNTDIR/home"
# @snapshots is mounted only for the build (to write the Factory Image); it's
# not in fstab, so the .snapshots mountpoint is removed from @ before detach.
mount -t btrfs -o "{{ $btrfsopts }},subvol=@snapshots" "$ROOTPART" "$IMAGEMNTDIR/.snapshots"
mount -t btrfs -o "{{ $btrfsopts }},subvol=@var-log" "$ROOTPART" "$IMAGEMNTDIR/var/log"
mount -t btrfs -o "{{ $btrfsopts }},subvol=@var-cache" "$ROOTPART" "$IMAGEMNTDIR/var/cache"

# debos writes the standard root line into /etc/fstab and root=UUID into the
# cmdline; the root subvolume is selected by the appended rootflags=subvol=@.
- action: filesystem-deploy
append-kernel-cmdline: {{ $cmdline }}
append-kernel-cmdline: rootflags=subvol=@ {{ $cmdline }}

# Add fstab entries for the non-root subvolumes (debos only generated the root
# line). The FSUUID is read back at build time. @snapshots is intentionally
# omitted -- it holds the Factory Image and is mounted on demand for rollback,
# never auto-mounted at runtime.
- action: run
description: Append subvolume entries to /etc/fstab
chroot: false
command: |
set -eu
ROOTPART="$({{ $rootdev }})"
FSUUID="$(blkid -o value -s UUID -p -c none "$ROOTPART")"
cat >> "$IMAGEMNTDIR/etc/fstab" <<EOF
UUID=$FSUUID /boot btrfs {{ $btrfsopts }},subvol=boot 0 0
UUID=$FSUUID /home btrfs {{ $btrfsopts }},subvol=@home 0 0
UUID=$FSUUID /var/log btrfs {{ $btrfsopts }},subvol=@var-log 0 0
UUID=$FSUUID /var/cache btrfs {{ $btrfsopts }},subvol=@var-cache 0 0
EOF
Comment thread
Yury-MonZon marked this conversation as resolved.

# journald's append-heavy writes fragment badly under btrfs copy-on-write. Pre-create
# the journal dir with NODATACOW (chattr +C) while it's still empty, so the journal
# files written on first boot inherit it. (+C also implies no compression.)
- action: run
description: Disable CoW on the systemd journal directory
chroot: false
command: |
set -eu
mkdir -p "$IMAGEMNTDIR/var/log/journal"
chattr +C "$IMAGEMNTDIR/var/log/journal"

- action: overlay
description: Copy Linux kernel packages into the target
Expand All @@ -43,3 +119,26 @@ actions:
description: Install Linux kernel packages inside the target
chroot: true
command: find /var/cache/linux-kernel -name linux-image-\*.deb -a ! -name \*-dbg_\* -print0 | xargs -0 apt install -y

# Read-only "Factory Image" snapshot of @, taken once the rootfs and kernel are
# fully deployed. It captures @ only -- the nested subvols (boot/home/var-*) are
# separate and excluded.
- action: run
description: Create the read-only Factory Image snapshot of @
chroot: false
command: btrfs subvolume snapshot -r "$IMAGEMNTDIR" "$IMAGEMNTDIR/.snapshots/@_Factory-Image"

# Unmount the nested subvolumes (deepest first), leaving @ mounted at
# $IMAGEMNTDIR. debos' image-partition Cleanup unmounts @ and detaches the loop;
# without this it would fail with EBUSY on the still-mounted children.
- action: run
description: Unmount nested subvolumes before debos detaches the loop
chroot: false
command: |
set -eu
sync
findmnt -rno TARGET --submounts "$IMAGEMNTDIR" \
| awk -v root="$IMAGEMNTDIR" '$0 != root' \
| awk '{ print length, $0 }' | sort -rn | cut -d" " -f2- \
| while read -r mp; do umount "$mp"; done
rmdir "$IMAGEMNTDIR/.snapshots"
3 changes: 3 additions & 0 deletions debian-rk3576-ospack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ actions:
- breeze-gtk-theme
- bsdextrautils
- bsdmainutils
- btrfs-compsize
Comment thread
Yury-MonZon marked this conversation as resolved.
- btrfs-progs
- build-essential
- cage
- ca-certificates
Expand All @@ -43,6 +45,7 @@ actions:
- dmidecode
- dnsmasq-base
- drm-info
- duperemove
- gstreamer1.0-plugins-bad
- gstreamer1.0-tools
- e2fsprogs
Expand Down
48 changes: 34 additions & 14 deletions overlays/configs/kernel/install.d/90-loaderentry.install
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ TITLE_MAX="${FLIPPER_TITLE_MAX:-26}"
DTB_DIR="/usr/lib/linux-image-$KERNEL_VERSION"
DTB_DIRS="$DTB_DIR /usr/lib/modules/$KERNEL_VERSION/dtb"

# U-Boot reads the btrfs TOP LEVEL (subvolid 5), so in-entry devicetreedir/overlay paths must
# be prefixed with the root subvolume the entry boots -- /@<subvol>/usr/lib/... -- where that
# root's DTBs live (the kernel package installs them in every root's /usr/lib). The prefix is
# derived per entry from its own rootflags=subvol= (each profile boots a different root). The
# staged kernel/initrd live under /boot (the boot subvolume) and are never prefixed.
# Set FDT_SUBVOL_PREFIX to override the derivation (e.g. "" if the root is not a subvolume).
fdt_prefix() { # $1 = options string -> "/<rootflags-subvol>" (or $FDT_SUBVOL_PREFIX if set)
if [ "${FDT_SUBVOL_PREFIX+x}" = x ]; then printf '%s' "$FDT_SUBVOL_PREFIX"; return 0; fi
_sv=$(printf '%s' "$1" | tr ' ' '\n' | sed -n 's/^rootflags=subvol=//p' | tail -n1)
[ -n "$_sv" ] && printf '/%s' "$_sv"
return 0
}
Comment thread
Yury-MonZon marked this conversation as resolved.

log() { [ "${KERNEL_INSTALL_VERBOSE:-0}" -gt 0 ] && echo "flipper-bls: $*" >&2; return 0; }
die() { echo "flipper-bls: $*" >&2; exit 1; }
trim() { printf '%s' "$1" | sed 's/^ *//; s/ *$//'; }
Expand All @@ -53,8 +66,12 @@ if [ "$COMMAND" = "remove" ]; then
fi
[ "$COMMAND" = "add" ] || exit 0

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

# absolute path under BOOT_ROOT -> path as it must appear inside a BLS entry (relative
# to the boot partition; identical to the absolute path when /boot is on the rootfs).
Expand Down Expand Up @@ -130,7 +147,7 @@ fi
# devicetreedir (U-Boot fdtdir extension) — referenced in place, opt-in via a
# /etc/kernel/devicetreedir file containing a true boolean. Only when no single
# devicetree was configured.
DEVICETREEDIR_SRC=""; DEVICETREEDIR_ENTRY=""
DEVICETREEDIR_SRC=""; DEVICETREEDIR_REL=""
if [ -z "$DEVICETREE" ] && [ -f "$CONF_ROOT/devicetreedir" ]; then
_v=""; read -r _v <"$CONF_ROOT/devicetreedir" || :
case "$_v" in
Expand All @@ -143,7 +160,7 @@ if [ -z "$DEVICETREE" ] && [ -f "$CONF_ROOT/devicetreedir" ]; then
[ -n "$DEVICETREEDIR_SRC" ] && break
done
[ -n "$DEVICETREEDIR_SRC" ] || die "no device-tree directory for $KERNEL_VERSION"
DEVICETREEDIR_ENTRY="$(boot_rel "$DEVICETREEDIR_SRC")"
DEVICETREEDIR_REL="$(boot_rel "$DEVICETREEDIR_SRC")" # /usr/lib/...; prefixed per entry
;;
esac
fi
Expand Down Expand Up @@ -180,6 +197,8 @@ done
# write_entry FILE TITLE OPTIONS FDTOVERLAYS
write_entry() {
_f="$1"; _title="$2"; _opts="$3"; _fdtov="$4"
# devicetreedir is prefixed with THIS entry's root subvol (U-Boot reads subvolid 5)
_dtdir=""; [ -n "$DEVICETREEDIR_REL" ] && _dtdir="$(fdt_prefix "$_opts")$DEVICETREEDIR_REL"
{
echo "# Boot Loader Specification type#1 entry (Flipper One)"
echo "title $_title"
Expand All @@ -188,21 +207,22 @@ write_entry() {
[ -n "$SORT_KEY" ] && echo "sort-key $SORT_KEY"
[ -n "$_opts" ] && echo "options $_opts"
echo "linux $KERNEL_ENTRY"
[ -n "$DEVICETREE_ENTRY" ] && echo "devicetree $DEVICETREE_ENTRY"
[ -n "$DEVICETREEDIR_ENTRY" ] && echo "devicetreedir $DEVICETREEDIR_ENTRY"
[ -n "$_fdtov" ] && echo "devicetree-overlay $_fdtov"
[ -n "$DEVICETREE_ENTRY" ] && echo "devicetree $DEVICETREE_ENTRY"
[ -n "$_dtdir" ] && echo "devicetreedir $_dtdir"
[ -n "$_fdtov" ] && echo "devicetree-overlay $_fdtov"
for _i in $INITRD_ENTRIES; do echo "initrd $_i"; done
} >"$_f"
return 0 # don't let a trailing empty conditional's status abort `set -e`
}

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

# Base entry == the [DEFAULT] profile.
write_entry "$ENTRY_STEM.conf" "$(make_title '[DEFAULT]')" "$BASE_OPTS" ""
# Base entry == the [DESKTOP] profile.
write_entry "$ENTRY_STEM.conf" "$(make_title '[DESKTOP]')" "$BASE_OPTS" ""

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

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

paths="$(overlay_paths $dtbos)" || paths=""
paths="$(overlay_paths "$(fdt_prefix "$opts")" $dtbos)" || paths=""
if [ -z "$paths" ] && [ "$required" = 1 ]; then
log "skip $tok (overlays not found for $KERNEL_VERSION)"
continue
Expand Down