Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flipper-profile

Bootable OS profiles for Flipper One, implemented as btrfs subvolumes plus overlayfs uppers over a read-only base. This is the working tool behind the proposal in flipperone-linux-build-scripts#80 — option (a) from that RFC, made concrete.

A profile is literally "an overlay on top of the base system": mount -t overlay -o lowerdir=@base/<ver>,upperdir=...,workdir=.... apt/dpkg run unmodified inside it; clone and reset are btrfs snapshots, so they cost nothing; base updates are whole new read-only subvolumes flipped into place atomically.

On-disk layout

One btrfs partition (the "pool"), top level mounted at /run/flipper/pool on a booted device:

@base/
  1.0/                  read-only Debian rootfs (btrfs subvolume)
  2.0/                  next version, received via update
  current -> 2.0        symlink, flipped atomically with rename(2)
@profiles/
  <id>/
    upper/              btrfs subvolume holding BOTH overlay dirs:
      root/               overlayfs upperdir — ALL profile state
      work/               overlayfs workdir
    snapshots/<tag>     read-only snapshots of upper (undo stack)
    profile.json        metadata: id, pinned base, origin, created
    base                pinned base version, one line (plain text so the
                        initramfs script reads it with `cat`)
@data                   shared user files, bind-mounted into every profile

Why upper/root + upper/work inside one subvolume: overlayfs renames files between workdir and upperdir, and rename(2) across btrfs subvolumes fails with EXDEV. Keeping both in the same subvolume also means one snapshot captures everything a profile owns.

Profiles pin the base version they were created against. Adopting a new base flips @base/current for future profiles; existing ones keep booting their pinned base until explicitly re-based. Old bases stay until GC'd, so base rollback is "flip the symlink back".

Commands

flipper-profile [--root DIR] <command>

--root defaults to /run/flipper/pool (env FLIPPER_PROFILE_ROOT).

command what it does
init <dev|dir> create @base @profiles @data subvolumes (mounts/unmounts the device if given one)
base-adopt <version> <stream|dir> install a base from a btrfs send stream (file or -) or a rootfs directory, mark it read-only, atomically flip @base/current
create <id> [--from <id>] new empty profile, or instant clone via snapshot
list profiles with pinned base, origin, exclusive disk usage (btrfs qgroups if quota is enabled, du fallback), snapshot count
mount <id> <mnt> assemble the profile overlay against its pinned base at <mnt>
snapshot [<id>] [--tag T] [--auto] read-only snapshot of the upper; --auto is the apt-hook mode: resolves the booted profile from /proc/cmdline, prunes to the last 5 auto-snapshots, and no-ops silently on non-profile boots
undo [<id>] restore the most recent snapshot and pop it off the stack (repeat to step further back)
reset <id> empty the upper — profile reverts to pristine base (snapshots kept)
delete <id> remove the profile and its snapshots
set-default <id> [--extlinux PATH] make the profile the default boot entry (see below)

Everything is python3 stdlib + btrfs-progs; run as root.

Boot integration

set-default rewrites extlinux.conf (path via --extlinux or env FLIPPER_EXTLINUX, default /boot/extlinux/extlinux.conf): it points default at the profile's label, creating the label by cloning the first existing entry and appending flipper.profile=<id> flipper.base=<ver> to its append line. This is the same file FlipCTL already rewrites for target switching in #68, so the two compose — one label per profile, FlipCTL flips default.

At boot, kernel cmdline carries:

root=<btrfs partition> rootflags=subvol=/ flipper.profile=<id> [flipper.base=<ver>]

initramfs/ ships the initramfs-tools pieces:

  • hooks/flipper-profile/etc/initramfs-tools/hooks/ — pulls the btrfs + overlay modules (and the btrfs binary, for rescue shells) into the initramfs.
  • scripts/local-bottom/flipper-profile/etc/initramfs-tools/scripts/local-bottom/ — after the root device is mounted, moves the pool to /run/flipper/pool, resolves the base (bootarg > profile pin > @base/current), mounts the overlay onto ${rootmnt}, bind-mounts @data, and falls back to booting the plain pool root if anything is missing (no profile bootarg = clean pass-through for non-profile setups).

initramfs-tools moves /run into the real root before switch_root, so the pool is already at /run/flipper/pool when the CLI runs in the booted system — no fstab entry needed.

Then update-initramfs -u.

apt rollback

apt/90flipper-profile/etc/apt/apt.conf.d/:

DPkg::Pre-Invoke { "/usr/sbin/flipper-profile snapshot --auto"; };

Every dpkg transaction gets a pre-snapshot of the booted profile's upper; flipper-profile undo rolls the last one back. On systems not booted through a profile the hook is a silent no-op, so it never breaks apt.

Testing

tests/e2e.sh (run with sudo) builds a 2 GB loopback btrfs image and exercises the full lifecycle: init → base-adopt from a stub rootfs → create/mount/write with upper-vs-base isolation checks → clone independence → snapshot/undo roundtrip → reset → second base adoption with atomic flip and pin verification → base-adopt from a real btrfs send stream → set-default rewriting a #68-style extlinux.conf → list/delete. 38 assertions, all green on btrfs-progs 6.x / kernel 7.x.

Scope — what this is not (yet)

  • No MCU handoff. The pre-Linux profile picker (MCU exposing the selection over I2C, boot.scr reading it) from the RFC is not here; set-default covers the FlipCTL-driven flow from #68 only.
  • No GC policy. Old bases and pruned profiles accumulate until deleted by hand; auto-snapshots are capped at 5 per profile but base versions are never reaped automatically.
  • No incremental base streams. base-adopt clears the received subvolume's read-only flag to rename it into place, which drops received_uuid — so btrfs send -p parent chaining doesn't work yet. Full streams only.
  • No flipper-profile check. The upper-vs-new-base conffile diff the RFC mentions for major base jumps is future work.
  • No per-profile /var / machine-id policy — currently whatever the overlay produces (i.e. isolated per profile once written).

License

MIT — see LICENSE.

About

btrfs+overlayfs OS profile manager for Flipper One (RFC flipperone-linux-build-scripts#80): bootable, clonable, disposable OS snapshots over a read-only base

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages