Skip to content

Latest commit

 

History

63 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LayerFS

Transactional, layered, recoverable root filesystem architecture for Linux.

LayerFS lets root modify a running system normally — edit config, remove binaries, overwrite libraries — while keeping every mutation redirected into layered storage that can be inspected, reset, or rolled back. It is not an immutable distribution: there is no sandbox, no confirmation prompt, no special syntax for sudo rm. The layering is invisible until you need it — recovering from a broken update, booting a known-good checkpoint, or inspecting what changed.

Status: early skeleton. Core semantics and CLI scaffolding exist; root assembly, the transaction engine, and package-manager adapters are not implemented yet. See SPECS.md for the design reference and ROADMAP.md for milestone-by-milestone progress. Where the code takes a different path than the specification describes, the code is authoritative and the divergence should be documented in commit messages or comments.

Architecture

OVERRIDE       RW, ordinary root mutations (upperdir)
UPDATE_HEAD    RO, most recent system transaction
UPDATE         RO, consolidated prior transactions
BASE           RO, original known-good system

These compose into an OverlayFS mount, highest layer wins. Four hardcoded boot checkpoints select a prefix of this stack:

checkpoint layers data override
base BASE no no
system UPDATE_HEAD > UPDATE > BASE no no
safe UPDATE_HEAD > UPDATE > BASE yes no
normal OVERRIDE > UPDATE_HEAD > UPDATE > BASE yes yes

layerfs.head=off drops UPDATE_HEAD from any checkpoint, giving exactly one system-update rollback without unbounded snapshot history.

Workspace layout

crates/
  layerfs-core         checkpoint/layer/state semantics, no I/O
  layerfs-storage       StorageBackend trait, Btrfs and directory backends
  layerfs-transaction    staging, locking, atomic commit
  layerfs-adapter        shared package-manager adapter runner (classify → passthrough or transaction)
  layerfs-init          early-userspace root assembly binary (initramfs)
  layerctl               administrative CLI

integrations/
  grub                   layerfs-grub-entries: generates the checkpoint GRUB menu entries
  dnf                    layerfs-dnf: dnf verb classification, built on layerfs-adapter
  dracut, apt, pacman     distro/init-system glue, not yet implemented

tests/
  integration    cross-crate filesystem tests
  qemu           boot-level integration tests

xtask/           developer task runner (musl builds, etc.)

Building

cargo build
cargo test

Distro/package-manager integrations (integrations/grub, integrations/dnf, ...) are separate crates excluded from the workspace's default-members, so the commands above stay distro-agnostic. Build one explicitly, or build everything with --workspace:

cargo build -p layerfs-dnf
cargo build --workspace

layerfs-init is intended for static musl builds:

cargo run -p xtask -- init-musl

OverlayFS assembly needs CAP_SYS_ADMIN to mount anything, so it isn't covered by cargo test. Verify it against a real kernel mount inside an unprivileged user+mount namespace instead — never against the host root:

cargo build -p layerfs-init --example overlay_smoke
unshare --map-root-user --mount -- \
    ./target/debug/examples/overlay_smoke /tmp/some-scratch-dir

For a stronger check — a real kernel, not a namespace — scripts/qemu-smoke.sh boots the host's own kernel under QEMU/KVM with layerfs-init's mount logic running as rdinit=/init in a throwaway initramfs, then powers off. Needs qemu-system-x86_64, a readable /boot/vmlinuz-*, and overlay.ko(.xz) under /lib/modules/$(uname -r); nothing on the host is modified.

./scripts/qemu-smoke.sh

scripts/qemu-btrfs-store-smoke.sh covers the backing-device path: it creates a throwaway Btrfs image in a privileged Docker container, attaches it as a virtio disk, and boots it under QEMU with layerfs.store=/dev/vda. The init mounts that device, assembles the root, and hands off to its init. It also needs Docker with permission to run privileged containers.

./scripts/qemu-btrfs-store-smoke.sh

scripts/qemu-grub-smoke.sh goes one step further: it generates a real grub.cfg with layerfs-grub-entries, checks it with grub2-script-check, builds an actual bootable ISO (grub2-mkrescue), and boots it under QEMU/KVM twice — once selecting the Normal entry, once selecting Base Recovery — proving GRUB itself renders the menu and passes the right layerfs.checkpoint= through to the kernel. Needs grub2-mkrescue and grub2-script-check in addition to the tools above.

./scripts/qemu-grub-smoke.sh

scripts/qemu-boot-artifacts-smoke.sh proves boot artifact selection specifically: registers the real host kernel under two distinguishable initramfs images as the BASE and HEAD boot generations, generates entries from that store, and boots both the Normal and Base Recovery entries under QEMU/KVM, checking each loaded the initramfs its own tier actually registered — not just that the generated paths look plausible.

./scripts/qemu-boot-artifacts-smoke.sh

scripts/qemu-systemd-boot-smoke.sh creates a temporary UEFI ESP and Btrfs store, then boots them through OVMF and systemd-boot under QEMU/KVM. It checks the BLS entry reaches the LayerFS root handoff and needs Docker, OVMF, and FAT filesystem tools in addition to the QEMU requirements above.

./scripts/qemu-systemd-boot-smoke.sh

For Debian and Ubuntu packaging, scripts/initramfs-tools-smoke.sh uses a throwaway Debian container to generate an initramfs with the LayerFS hook and checks that the binary plus OverlayFS and Btrfs modules are present.

./scripts/initramfs-tools-smoke.sh

layerctl transaction -- <program> [args...] drives the real transaction engine (staging, a private mount namespace, chrooted execution, validation, atomic commit) for development, in place of a package-manager adapter. Like OverlayFS assembly, it needs CAP_SYS_ADMIN/CAP_SYS_CHROOT:

unshare --map-root-user --mount -- \
    ./target/debug/layerctl --store /path/to/a/store transaction -- /bin/some-static-binary

layerctl rollback update discards the active UPDATE_HEAD, the one-step rollback the design allows (see "One-update rollback" above) — the prior UPDATE_HEAD was already squashed into UPDATE by the transaction that superseded it, so UPDATE alone is enough to boot from. Refuses if there's no UPDATE_HEAD to discard:

./target/debug/layerctl --store /path/to/a/store rollback update

layerctl install --source <dir> converts a static (not live) source tree into base/override/data, extracting home/root/srv out into data. Not the full live-migration flow (that needs a reboot into a dedicated dracut initramfs, not implemented yet). --integrations dnf,apt symlinks each present real package-manager binary (e.g. usr/bin/dnf) to its adapter (layerfs-dnf) inside the new base, matching what GRUB bakes into layerfs.integrations= on the boot entry (see integrations/grub). --grub-entries <path> copies a built layerfs-grub-entries binary into the new base's etc/grub.d/41_layerfs, executable, so grub2-mkconfig picks it up on the installed system:

./target/debug/layerctl --store /path/to/a/store install --source /path/to/a/rootfs \
    --integrations dnf --grub-entries ./target/debug/layerfs-grub-entries

A committed transaction only becomes the booted root on the next reboot — the running system's mounted / can't be reconfigured under it. But a committed update can be applied to the running system without a reboot: layerctl apply-now [--live-root <path>] snapshots the affected subtree, layers the store's UPDATE_HEAD/UPDATE on top, and atomically swaps it in via mount --move. This is scoped to /usr and /opt only — never / itself — since a whole-root swap could orphan mounts nested under paths like /proc or /home that a non-recursive snapshot bind mount wouldn't capture. Already-open files on already-running processes keep their old content (normal Unix replace-while-open behavior); new opens see the change immediately. dnf/apt adapters call this automatically after a commit, but only when the update stays within usr/opt and didn't touch a shared library, /boot, or systemd itself — otherwise they report that a reboot is required rather than risk leaving running processes on a stale version of something they already loaded, or orphaning a mount:

unshare --map-root-user --mount -- \
    ./target/debug/layerctl --store /path/to/a/store apply-now

Initial target

Fedora, Btrfs, GRUB, dracut, DNF, x86_64, UEFI. Other distributions and backends come after this configuration is proven; see SPECS.md section 32 and ROADMAP.md's Milestone 10 for the staged milestone plan.

Non-goals

Not a kernel filesystem, not a package manager, not a container runtime, not an immutable distribution, not a security boundary against a hostile root user. LayerFS protects against accidental or logical system damage, not deliberate bypass by someone with root and raw block-device access.

License

Apache-2.0. See LICENSE.

About

Transactional, layered, recoverable root filesystem architecture for Linux.

Resources

Stars

26 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages