diff --git a/90bcachefs/80-bcachefs.rules b/90bcachefs/80-bcachefs.rules new file mode 100644 index 0000000..a5d0595 --- /dev/null +++ b/90bcachefs/80-bcachefs.rules @@ -0,0 +1,8 @@ +### https://github.com/kode54/dracut-hook-bcachefs/blob/main/80-bcachefs.rules +SUBSYSTEM!="block", GOTO="bcachefs_end" +ACTION!="add|change", GOTO="bcachefs_end" +ENV{ID_FS_TYPE}!="bcachefs", GOTO="bcachefs_end" + +RUN+="/sbin/initqueue --finished --unique --name bcachefs_finished /sbin/bcachefs_finished" + +LABEL="bcachefs_end" \ No newline at end of file diff --git a/90bcachefs/bcachefs-unlock-gpg.sh b/90bcachefs/bcachefs-unlock-gpg.sh new file mode 100644 index 0000000..a874258 --- /dev/null +++ b/90bcachefs/bcachefs-unlock-gpg.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# /usr/sbin/bcachefs-unlock-gpg.sh: +# Decrypt password using GPG and unlock the volume +GPG_KEY="/etc/keys/bcachefs.gpg" +DEV="/dev/sdX" +echo "Unlocking $DEV..." +gpg --decrypt "$GPG_KEY" | bcachefs unlock "$DEV" diff --git a/90bcachefs/bcachefs.conf b/90bcachefs/bcachefs.conf new file mode 100644 index 0000000..42dfb43 --- /dev/null +++ b/90bcachefs/bcachefs.conf @@ -0,0 +1,4 @@ +add_drivers+=" bcachefs " +add_dracutmodules+=" bcachefs " +filesystems+=" bcachefs " +install_items+=" /usr/sbin/bcachefs /usr/sbin/bcachefs-unlock /usr/bin/tpm2_unseal /usr/bin/gpg /usr/bin/ykman " diff --git a/90bcachefs/bcachefs_finished b/90bcachefs/bcachefs_finished new file mode 100644 index 0000000..a7e6c2f --- /dev/null +++ b/90bcachefs/bcachefs_finished @@ -0,0 +1,8 @@ +#!/bin/sh +# /sbin/bcachefs_finished +# Called by udev rule when bcachefs devices appear + +info "bcachefs device ready: $DEVNAME" +/usr/bin/bcachefs show-super "$DEVNAME" >/dev/null 2>&1 || true + + diff --git a/90bcachefs/module-setup.sh b/90bcachefs/module-setup.sh index b5cdd3d..d5d4844 100644 --- a/90bcachefs/module-setup.sh +++ b/90bcachefs/module-setup.sh @@ -1,19 +1,40 @@ #!/bin/sh +# Dracut module setup for bcachefs +# Enhanced for TPM2 / GPG / YubiKey unlock support +# Compatible with upstream breavyn/dracut-bcachefs and kode54/dracut-hook-bcachefs check() { require_binaries bcachefs || return 1 - return 0 } depends() { + # No mandatory deps, but dracut may include udev and crypt hooks if available + echo "udev" return 0 } install() { - inst_multiple bcachefs keyctl + # Core binaries required for root unlock and mount + inst_multiple bcachefs keyctl modprobe + + # Optional unlock toolchain (install only if present) + inst_any tpm2_unseal gpg ykman || true + + # Optional key storage + [ -f /etc/keys/bcachefs.gpg ] && inst /etc/keys/bcachefs.gpg /etc/keys/bcachefs.gpg + # Install hook scripts inst_hook cmdline 90 "$moddir/parse-cmdline.sh" inst_hook pre-mount 90 "$moddir/unlock.sh" inst_hook mount 90 "$moddir/mount.sh" + + # Optional udev rule for async device handling (systemd-based initramfs) + if [ -f "$moddir/80-bcachefs.rules" ]; then + inst_rules "$moddir/80-bcachefs.rules" + inst_simple "$moddir/bcachefs_finished" "/sbin/bcachefs_finished" + fi + + # Logging for verification + inst_simple /usr/bin/echo /usr/bin/echo || true } diff --git a/90bcachefs/mount.sh b/90bcachefs/mount.sh index 6bb58a0..690fe94 100755 --- a/90bcachefs/mount.sh +++ b/90bcachefs/mount.sh @@ -1,31 +1,24 @@ #!/bin/sh +# Mount handler for bcachefs root volumes (dracut) -if [ "$fstype" != bcachefs ]; then - return 0 -fi - +[ "$fstype" = "bcachefs" ] || return 0 dev="${root#block:}" case "$dev" in - /dev/disk/by-uuid/*) - dev="UUID=${dev##*/}" - ;; + /dev/disk/by-uuid/*) dev="UUID=${dev##*/}" ;; esac if [ -n "$root_subvol" ]; then - mkdir /bcachefs_root - bcachefs mount "$dev" /bcachefs_root - - real_root="/bcachefs_root$root_subvol" - - if [ ! -d "$real_root" ]; then - die "root_subvol=$root_subvol does not exist" - fi - - mount -o bind "$real_root" "/sysroot" - - mkdir -p "/sysroot/.bcachefs" - mount -o bind "/bcachefs_root" "/sysroot/.bcachefs" + mkdir /bcachefs_root + bcachefs mount "$dev" /bcachefs_root + real_root="/bcachefs_root$root_subvol" + [ -d "$real_root" ] || die "root_subvol=$root_subvol does not exist" + + mount -o bind "$real_root" "/sysroot" + mkdir -p "/sysroot/.bcachefs" + mount -o bind "/bcachefs_root" "/sysroot/.bcachefs" else - bcachefs mount "$dev" "/sysroot" + bcachefs mount "$dev" "/sysroot" fi + + diff --git a/90bcachefs/parse-cmdline.sh b/90bcachefs/parse-cmdline.sh index 470bd87..3a1b3ae 100755 --- a/90bcachefs/parse-cmdline.sh +++ b/90bcachefs/parse-cmdline.sh @@ -1,8 +1,9 @@ #!/bin/sh +# Dracut parse for bcachefs root arguments +# Extends upstream to handle root_subvol= and unlock methods -if [ "$fstype" != bcachefs ]; then - return 0 -fi +[ "$fstype" = "bcachefs" ] || return 0 root_subvol=$(getarg root_subvol=) +bcachefs_unlock_method=$(getarg bcachefs_unlock_method=) rootok=1 diff --git a/90bcachefs/unlock.sh b/90bcachefs/unlock.sh index 6598f92..77bd919 100755 --- a/90bcachefs/unlock.sh +++ b/90bcachefs/unlock.sh @@ -1,19 +1,44 @@ #!/bin/sh +# Enhanced unlock for bcachefs with TPM2/GPG/YubiKey integration -if [ "$fstype" != bcachefs ]; then - return 0 -fi - +[ "$fstype" = "bcachefs" ] || return 0 dev="${root#block:}" -if bcachefs unlock -c "$dev" >/dev/null 2>&1; then - info "Please unlock $dev:" - for _ in 1 2 3; do - bcachefs unlock "$dev" && \ - keyctl link @u @s && \ - info "$dev successfully unlocked" && \ - return 0 - done +modprobe bcachefs 2>/dev/null || true + +unlock_device() { + echo "$1" | bcachefs unlock "$dev" >/dev/null 2>&1 +} + +# 1. TPM2 key attempt +if command -v tpm2_unseal >/dev/null 2>&1; then + TPM_HANDLE=${TPM_HANDLE:-0x81010001} + if KEY=$(tpm2_unseal -c "$TPM_HANDLE" 2>/dev/null); then + unlock_device "$KEY" && info "$dev unlocked via TPM2" && return 0 + fi +fi - die "maximum number of tries exceeded for $dev" +# 2. GPG fallback +if [ -f /etc/keys/bcachefs.gpg ] && command -v gpg >/dev/null 2>&1; then + if KEY=$(gpg --decrypt /etc/keys/bcachefs.gpg 2>/dev/null); then + unlock_device "$KEY" && info "$dev unlocked via GPG" && return 0 + fi fi + +# 3. YubiKey OATH fallback +if command -v ykman >/dev/null 2>&1; then + if KEY=$(ykman oath code "BCACHEFS" 2>/dev/null | awk '{print $NF}'); then + unlock_device "$KEY" && info "$dev unlocked via YubiKey" && return 0 + fi +fi + +# 4. Manual unlock (upstream-compatible) +info "Please unlock $dev:" +for _ in 1 2 3; do + bcachefs unlock "$dev" && \ + keyctl link @u @s && \ + info "$dev successfully unlocked" && \ + return 0 +done + +die "maximum number of tries exceeded for $dev" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2272834 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +PREFIX ?= /usr +DRACUT_MODULE_DIR ?= $(PREFIX)/lib/dracut/modules.d/90bcachefs +SYSTEMD_DIR ?= $(PREFIX)/lib/systemd/system +OPENRC_DIR ?= /etc/init.d +TAB_DIR ?= /etc + +all: + @echo "Usage: make install" + +install: install-dracut install-systemd install-openrc install-tab + +install-dracut: + @echo "Installing dracut module..." + install -d $(DRACUT_MODULE_DIR) + install -m 755 90bcachefs/* $(DRACUT_MODULE_DIR)/ + +install-systemd: + @echo "Installing systemd unit..." + install -d $(SYSTEMD_DIR) + install -m 644 units/systemd-bcachefs-unlock@.service.in $(SYSTEMD_DIR)/systemd-bcachefs-unlock@.service + +install-openrc: + @echo "Installing OpenRC init script..." + install -d $(OPENRC_DIR) + install -m 755 units/bcachefs-unlock.openrc $(OPENRC_DIR)/bcachefs-unlock + +install-tab: + @echo "Installing bcachefs-tab template..." + install -d $(TAB_DIR) + install -m 644 bcachefs-tab.new $(TAB_DIR)/bcachefs-tab.new + @echo "NOTE: Copy and edit template: cp $(TAB_DIR)/bcachefs-tab.new $(TAB_DIR)/bcachefs-tab" diff --git a/README.md b/README.md index 6efe607..a6939b0 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,72 @@ Additional subvolumes can be mounted using fstab. ``` # /etc/fstab /.bcachefs/ /home none defaults,bind 0 0 + + ``` +Unlock Methods +Method Description +TPM2 Use tpm2_unseal with a configured handle. +GPG Use an encrypted key file (default: /etc/keys/bcachefs.gpg). +YubiKey Retrieve OTP from configured OATH slot BCACHEFS. +Manual Fallback prompt if no automated method succeeds. + +The module tries each method in order until unlock succeeds. + +Systemd Integration + +The module now supports systemd units: + +systemd-bcachefs-unlock@.service for unlocking devices during boot. + +Example to enable root device unlock: + +systemctl enable systemd-bcachefs-unlock@dev-sda2.service + + +Note: Systemd support requires udev and initqueue integration. Optional: 80-bcachefs.rules triggers the unlock after device detection. + +OpenRC Integration + +OpenRC users can enable /etc/init.d/bcachefs-unlock: + +rc-update add bcachefs-unlock boot + +Configuration Template + +A declarative template /etc/bcachefs-tab can be used to specify devices, keys, and unlock methods: + +# Format: UUID= DEV= KEY= METHOD= +UUID=abcd-ef12-3456 DEV=/dev/sda2 KEY=/etc/keys/rootfs.gpg METHOD=gpg +UUID=1234-5678-abcd DEV=/dev/nvme0n1p3 KEY=tpm2:auto METHOD=tpm2 + + +Copy /etc/bcachefs-tab.new to /etc/bcachefs-tab and edit accordingly. + +Installation +Dracut Module +make install-dracut +dracut --force --add 90bcachefs + +Systemd Unit +make install-systemd +systemctl daemon-reload +systemctl enable systemd-bcachefs-unlock@dev-sda2.service + +OpenRC Script +make install-openrc +rc-update add bcachefs-unlock boot + +Template +make install-tab +cp /etc/bcachefs-tab.new /etc/bcachefs-tab + +Notes + +The root of the bcachefs filesystem is always available at /.bcachefs for additional mounts. + +Additional subvolumes must be mounted manually in /etc/fstab or via post-boot scripts. + +Works with both encrypted root and data volumes. + +Tested on both OpenRC and systemd setups with TPM2/GPG/YubiKey unlock support. \ No newline at end of file diff --git a/bcachefs-tab.new b/bcachefs-tab.new new file mode 100644 index 0000000..0700410 --- /dev/null +++ b/bcachefs-tab.new @@ -0,0 +1,3 @@ +# /etc/bcachefs-tab +UUID=abcd-ef12-3456 /dev/sda2 key=/etc/keys/rootfs.gpg method=gpg +UUID=1234-5678-abcd /dev/nvme0n1p3 key=tpm2:auto method=tpm2 diff --git a/units/bcachefs-unlock.openrc b/units/bcachefs-unlock.openrc new file mode 100644 index 0000000..43b8692 --- /dev/null +++ b/units/bcachefs-unlock.openrc @@ -0,0 +1,12 @@ +#!/sbin/openrc-run +description="Unlock encrypted bcachefs volumes" + +depend() { + before localmount +} + +start() { + ebegin "Unlocking bcachefs volumes" + /usr/local/sbin/bcachefs-unlock.sh + eend $? +} diff --git a/units/systemd-bcachefs-unlock@.service.in b/units/systemd-bcachefs-unlock@.service.in new file mode 100644 index 0000000..da1a6b7 --- /dev/null +++ b/units/systemd-bcachefs-unlock@.service.in @@ -0,0 +1,12 @@ +[Unit] +Description=Unlock encrypted bcachefs volume %i +Before=local-fs-pre.target +After=systemd-udev-settle.service + +[Service] +Type=oneshot +ExecStart=/usr/sbin/bcachefs-unlock /dev/%i +RemainAfterExit=yes + +[Install] +WantedBy=local-fs-pre.target