diff --git a/.ci/shellcheck.sh b/.ci/shellcheck.sh
index 1a1625af5b227506fcf4a0707a63a45535132f9a..dea9fba8cc3d91198451eabca83a0fc4b126ec17 100755
--- a/.ci/shellcheck.sh
+++ b/.ci/shellcheck.sh
@@ -20,7 +20,6 @@ sh_files="
 	./main/postmarketos-installkernel/installkernel-pmos
 	./main/postmarketos-initramfs/init.sh
 	./main/postmarketos-initramfs/init_functions.sh
-	./main/postmarketos-mkinitfs-hook-debug-shell/20-debug-shell.sh
 	./main/postmarketos-mkinitfs-hook-debug-shell/setup_usb_storage.sh
 	./main/postmarketos-mkinitfs-hook-netboot/netboot.sh
 	./main/ttyescape/*.post-install
diff --git a/main/postmarketos-initramfs/APKBUILD b/main/postmarketos-initramfs/APKBUILD
index afd5f84ef880a06f93563a6e606de6d6c63a23a8..3c268c3db8e81e3d257b9fd3d527dd688104965c 100644
--- a/main/postmarketos-initramfs/APKBUILD
+++ b/main/postmarketos-initramfs/APKBUILD
@@ -1,7 +1,7 @@
 # Maintainer: Oliver Smith <ollieparanoid@postmarketos.org>
 # Co-Maintainer: Clayton Craft <clayton@craftyguy.net>
 pkgname=postmarketos-initramfs
-pkgver=2.5.6
+pkgver=2.6.0
 pkgrel=0
 pkgdesc="Base files for the postmarketOS initramfs / initramfs-extra"
 url="https://postmarketos.org"
@@ -89,9 +89,10 @@ sha512sums="
 59be0649ed87a72d93624bd8a2e3f8c99a0f32f7b7a26f99436de782beba55671472c269eeee86440efc87e0d7148a0bb335fa537791092e73878ca21330544a  00-default.modules
 5b364300f31c91fd0591eb0715f67cbf5383f45246a5fb9f34b79f7cb2e3b15768b2130e5f32f816cc169950f988c1beabc879ba31645c58ce131a288dbc071d  00-initramfs-base.dirs
 ab41b45b0613f25a61114ed8c8b92bc53c60838f6e2e0ba18c76e5369b2984e6023a0661887692673aca3f647f268c468a468f6b1ac424cfee609017a89481dd  00-initramfs-base.files
+ede9eb44fe180578ca97b7abfa2c4f1b4e277a49e4ca8b8911005acd844dbce38f200bc84280ded5a35fe5abd32bec391efc4af1929db12aa52da0c1036c6983  00-initramfs-fullsize.files
 8a4adad3785af474b36a09a05f6a3b2c4b4f43aac331a53b903abfa51ea12be1e3d1d807b7a6e66a1346815f3b0044daf8cd62e21e2dc75d2db13ee265a72985  00-initramfs-extra-base.files
-b42d7cceac11f4d017b9f8eaa86ec34c4924cc04f61cec7dfd5f0b30465b3a019a1d7fc50a13f3df53531f2afc314f50ac74611d6fd5ecf91c293039bcdc2fa8  init.sh
-71c180ef0030e4dec2e20336efa354d08ced8cbf6825b1c0bdb527b349040c73fdc87c287e542c68bca1bb4678266a1fb5dd869b538fced032361c7c887b7894  init_functions.sh
+42ff1c71326ce8ad1f023031b3aa3d96e2b6ab5daf035a363f59a9c17255a348d59adbe808d44f6c31cb03a1c4a0278111c8b7842b7cf1854c1693db3836b7a9  init.sh
+167e3ebdc6f7bc77e0d3f03c671a90ce14c3979914b377f86efdcbf38ccd7c8ebf91043831fd9d9cfbcd5ce99b360dd4088d2935b1b39633e57c2a626bbc3b99  init_functions.sh
 ba3275a9af788c7c782322a22a0f144d5e50e3498ea6886486a29331f23ae89cd32d500a3635cfa7cab369afba92edc18aeca64ccbf0cd589061cce23d15b46c  unudhcpd.conf
 675e7d5bee39b2df7d322117f8dcaccc274d61beaf4d50ead19bbf2109446d64b1c0aa0c5b4f9846eb6c1c403418f28f6364eff4537ba41120fbfcbc484b7da7  mdev.conf
 "
diff --git a/main/postmarketos-initramfs/init.sh b/main/postmarketos-initramfs/init.sh
index c047fb07393d8ff4e1150b36fbb5ed5ed21c74e4..4ef435f10c5dfd72df1588efbf103184ac399332 100644
--- a/main/postmarketos-initramfs/init.sh
+++ b/main/postmarketos-initramfs/init.sh
@@ -2,7 +2,7 @@
 # shellcheck disable=SC1091
 
 IN_CI="false"
-LOG_PREFIX="[pmOS.rd]"
+LOG_PREFIX="[pmOS-rd]"
 
 [ -e /hooks/10-verbose-initfs.sh ] && set -x
 
@@ -46,12 +46,17 @@ if [ "$IN_CI" = "true" ]; then
 	fail_halt_boot
 fi
 
-# Always run dhcp daemon/usb networking for now (later this should only
-# be enabled, when having the debug-shell hook installed for debugging,
-# or get activated after the initramfs is done with an OpenRC service).
 setup_usb_network
 start_unudhcpd
 
+# if iskey isn't available then symlink false -- assume no keys
+# are pressed
+if [ ! -x /usr/bin/iskey ]; then
+	ln -s /bin/false /usr/bin/iskey
+fi
+
+check_keys true
+
 mount_subpartitions
 
 wait_boot_partition
@@ -95,6 +100,13 @@ echo ratelimit > /proc/sys/kernel/printk_devkmsg
 
 killall telnetd mdev udevd msm-fb-refresher syslogd 2>/dev/null
 
+# Kill any getty shells that might be running
+for pid in $(pidof sh); do
+	if ! [ "$pid" = "1" ]; then
+		kill -9 "$pid"
+	fi
+done
+
 # shellcheck disable=SC2093
 exec switch_root /sysroot "$init"
 
diff --git a/main/postmarketos-initramfs/init_functions.sh b/main/postmarketos-initramfs/init_functions.sh
index 6b5695e6db59384e22cc771f9394a0fa536f7f58..b98d893d40d2d82b20a78a8e4a3556494b3891e1 100644
--- a/main/postmarketos-initramfs/init_functions.sh
+++ b/main/postmarketos-initramfs/init_functions.sh
@@ -6,6 +6,9 @@ PMOS_BOOT=""
 PMOS_ROOT=""
 
 CONFIGFS=/config/usb_gadget
+CONFIGFS_ACM_FUNCTION="acm.usb0"
+
+deviceinfo_codename=""
 
 # Redirect stdout and stderr to logfile
 setup_log() {
@@ -29,9 +32,6 @@ setup_log() {
 	# Process substitution is technically non-POSIX, but is supported by busybox
 	# shellcheck disable=SC3001
 	exec > >(tee /pmOS_init.log "$pmsg" | logger -t "$LOG_PREFIX" -p user.info) 2>&1
-
-	# Say hello
-	echo "### postmarketOS initramfs ###"
 }
 
 mount_proc_sys_dev() {
@@ -48,6 +48,7 @@ mount_proc_sys_dev() {
 	mkdir -p /dev/pts
 	mount -t devpts devpts /dev/pts
 
+	# This is required for process substitution to work (as used in setup_log())
 	ln -s /proc/self/fd /dev/fd
 }
 
@@ -732,6 +733,157 @@ start_unudhcpd() {
 	) &
 }
 
+setup_usb_acm_configfs() {
+	active_udc="$(cat $CONFIGFS/g1/UDC)"
+
+	if ! [ -e "$CONFIGFS" ]; then
+		echo "  /config/usb_gadget does not exist, can't set up serial gadget"
+		return 1
+	fi
+
+	# unset UDC
+	echo "" > /config/usb_gadget/g1/UDC
+
+	# Create acm function
+	mkdir "$CONFIGFS/g1/functions/$CONFIGFS_ACM_FUNCTION" \
+		|| echo "  Couldn't create $CONFIGFS/g1/functions/$CONFIGFS_ACM_FUNCTION"
+
+	# Link the acm function to the configuration
+	ln -s "$CONFIGFS/g1/functions/$CONFIGFS_ACM_FUNCTION" "$CONFIGFS/g1/configs/c.1" \
+		|| echo "  Couldn't symlink $CONFIGFS_ACM_FUNCTION"
+
+	# Reconfigure the UDC
+	setup_usb_configfs_udc
+
+	return 0
+}
+
+# Spawn a subshell to restart the getty if it exits
+# $1: tty
+run_getty() {
+	{
+		while /sbin/getty -n -l /sbin/pmos_getty "$1" 115200 vt100; do
+			sleep 0.2
+		done
+	} &
+}
+
+debug_shell() {
+	echo "Entering debug shell"
+	setup_usb_acm_configfs
+
+	# mount pstore, if possible
+	if [ -d /sys/fs/pstore ]; then
+		mount -t pstore pstore /sys/fs/pstore || true
+	fi
+
+	mount -t debugfs none /sys/kernel/debug || true
+	# make a symlink like Android recoveries do
+	ln -s /sys/kernel/debug /d
+
+	cat <<-EOF > /README
+	postmarketOS debug shell
+	https://postmarketos.org/debug-shell
+
+	  Kernel: $(uname -r)
+	  Device: $deviceinfo_codename
+	  OS ver: $VERSION
+	  initrd: $INITRAMFS_PKG_VERSION
+
+	Run 'pmos_continue_boot' to continue booting.
+	Run 'pmos_logdump' to generate a log dump and expose it over USB.
+	EOF
+
+	if [ -f /usr/bin/setup_usb_storage ]; then
+		cat <<-EOF >> /README
+		You can expose storage devices over USB with
+		'setup_usb_storage /dev/DEVICE'
+		EOF
+	fi
+
+	# Display some info
+	cat <<-EOF > /etc/profile
+	cat /README
+	. /init_functions.sh
+	EOF
+
+	cat <<-EOF > /sbin/pmos_getty
+	#!/bin/sh
+	exec /bin/sh -l
+	EOF
+	chmod +x /sbin/pmos_getty
+
+	cat <<-EOF > /sbin/pmos_continue_boot
+	#!/bin/sh
+	echo "Continuing boot..."
+	touch /tmp/continue_boot
+	while sleep 1; do :; done
+	EOF
+	chmod +x /sbin/pmos_continue_boot
+
+	cat <<-EOF > /sbin/pmos_logdump
+	#!/bin/sh
+	echo "Dumping logs, check for a new mass storage device"
+	touch /tmp/dump_logs
+	EOF
+	chmod +x /sbin/pmos_logdump
+
+	# Get the console (ttyX) associated with /dev/console
+	active_console="$(cat /sys/devices/virtual/tty/console/active)"
+
+	# Spawn a getty on the active console
+	run_getty "$active_console"
+
+	# Spawn fbkeyboard if installed
+	if [ -x /usr/bin/fbkeyboard ]; then
+		modprobe uinput
+		fbkeyboard -r "$(cat /sys/class/graphics/fbcon/rotate)" \
+			2>/dev/tty1 &
+		if [ "$active_console" != "tty1" ]; then
+			run_getty tty1
+		fi
+	else
+		show_splash "WARNING: debug-shell is active\\nhttps://postmarketos.org/debug-shell"
+	fi
+
+	# And on the usb acm port (if it exists)
+	if [ -e /dev/ttyGS0 ] && [ "$active_console" != "ttyGS0" ]; then
+		run_getty ttyGS0
+	fi
+
+	# wait until we get the signal to continue boot
+	while ! [ -e /tmp/continue_boot ]; do
+		sleep 0.2
+		if [ -e /tmp/dump_logs ]; then
+			rm -f /tmp/dump_logs
+			export_logs
+		fi
+	done
+
+	# Remove the ACM gadget device
+	rm -f $CONFIGFS/g1/configs/c.1/"$CONFIGFS_ACM_FUNCTION"
+	rmdir $CONFIGFS/g1/functions/"$CONFIGFS_ACM_FUNCTION"
+	setup_usb_configfs_udc
+
+	show_splash "Loading..."
+
+	pkill -f fbkeyboard || true
+}
+
+# Check if the user is pressing a key and either drop to a shell or halt boot as applicable
+# $1: If set, also trigger debug shell if "pmos.debug-shell" is in kernel cmdline
+check_keys() {
+	# If the user is pressing either the left control key or the volume down
+	# key then drop to a debug shell.
+	if { [ -n "$1" ] && grep -q "pmos.debug-shell" /proc/cmdline; } || iskey KEY_LEFTCTRL KEY_VOLUMEDOWN; then
+		debug_shell
+	# If instead they're pressing left shift or volume up, then fail boot
+	# and dump logs
+	elif iskey KEY_LEFTSHIFT KEY_VOLUMEUP; then
+		fail_halt_boot
+	fi
+}
+
 # $1: Message to show
 show_splash() {
 	# Skip for non-framebuffer devices
diff --git a/main/postmarketos-mkinitfs-hook-console-shell/APKBUILD b/main/postmarketos-mkinitfs-hook-console-shell/APKBUILD
index ef7eeb232cf2d546e5991d1aa98c80896e66048d..853ab3b180e0f3e852783b9dcf9443b46d60cdeb 100644
--- a/main/postmarketos-mkinitfs-hook-console-shell/APKBUILD
+++ b/main/postmarketos-mkinitfs-hook-console-shell/APKBUILD
@@ -1,18 +1,16 @@
 # Maintainer: Ferenc Bakonyi <bakonyi.ferenc@gmail.com>
 pkgname=postmarketos-mkinitfs-hook-console-shell
-pkgver=0.3.1
+pkgver=0.4.0
 pkgrel=0
 pkgdesc="Root console shell in the initramfs (security hole, for debugging only)"
 url="https://postmarketos.org"
 depends="postmarketos-mkinitfs devicepkg-utils fbdebug evtest linuxconsoletools reboot-mode fbkeyboard font-dejavu"
-source="console-shell.sh console-shell.files console-shell.modules"
+source="console-shell.files console-shell.modules"
 arch="noarch"
 license="GPL-2.0-or-later"
 options="!check" # No tests
 
 package() {
-	install -Dm644 "$srcdir"/console-shell.sh \
-		"$pkgdir"/usr/share/mkinitfs/hooks/30-console-shell.sh
 	install -Dm644 "$srcdir"/console-shell.files \
 		"$pkgdir"/usr/share/mkinitfs/files/30-console-shell.files
 	install -Dm644 "$srcdir"/console-shell.modules \
@@ -20,7 +18,6 @@ package() {
 }
 
 sha512sums="
-0753bf2e2bb011309a906db08b5fc13dba90e55bd5d11c8da0c34c89faf04dbf5915785cc0ed2a164f2e4c5fede06c7e80b5f592f4deb306cf5cec2371ef88f9  console-shell.sh
 17b65cb24103e4c1459ae72bc036c1f06cdfcccf480389ecf6a28253d104b9b06d394cf53314a1ef4ace4ffc88b6b1384ef4894b7270d6b2cfdfc83592e12b2c  console-shell.files
 a9b069ed121ffeee887e0583d8cb46035ecf1fa90a26a4ecb3aa11ff03178b2b08621f6676db6b2350f290694c04aabcf36f2ce3e0813a76dde9a33555edb112  console-shell.modules
 "
diff --git a/main/postmarketos-mkinitfs-hook-console-shell/console-shell.sh b/main/postmarketos-mkinitfs-hook-console-shell/console-shell.sh
deleted file mode 100644
index 62ad9388204757337673f4597511e2b52464b5f6..0000000000000000000000000000000000000000
--- a/main/postmarketos-mkinitfs-hook-console-shell/console-shell.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-# shellcheck disable=SC1091
-. ./init_functions.sh
-. /usr/share/misc/source_deviceinfo
-
-# mount pstore, if possible
-if [ -d /sys/fs/pstore ]; then
-	mount -t pstore pstore /sys/fs/pstore || true
-fi
-
-if tty -s; then
-	tty=/dev/tty0
-	modprobe uinput
-	fbkeyboard -r $(cat /sys/class/graphics/fbcon/rotate) 2>$tty &
-	echo "Exit the shell to continue booting:" > $tty
-	sh +m <$tty >$tty 2>$tty
-	pkill -f fbkeyboard
-else
-	echo "No tty attached, exiting."
-fi
diff --git a/main/postmarketos-mkinitfs-hook-debug-shell/20-debug-shell.sh b/main/postmarketos-mkinitfs-hook-debug-shell/20-debug-shell.sh
deleted file mode 100644
index 59496c503e9c2bb2e2565eab6d42848cc19ab38a..0000000000000000000000000000000000000000
--- a/main/postmarketos-mkinitfs-hook-debug-shell/20-debug-shell.sh
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/bin/sh
-# shellcheck disable=SC1091
-. ./init_functions.sh
-. /usr/share/misc/source_deviceinfo
-TELNET_PORT=23
-
-setup_usb_network
-start_unudhcpd
-
-show_splash "WARNING: debug-shell is active\\nhttps://postmarketos.org/debug-shell"
-
-echo "Create 'pmos_continue_boot' script"
-{
-	echo "#!/bin/sh"
-	#Disable any active usb mass storage
-	echo "if [ -d /config/usb_gadget/g1/functions/mass_storage.0 ]; then setup_usb_storage; fi"
-	echo "pkill -9 -f pmos_shell"
-	echo "pkill -f pmos_fail_halt_boot"
-	echo "pkill -f telnetd.*:${TELNET_PORT}"
-} >/usr/bin/pmos_continue_boot
-chmod +x /usr/bin/pmos_continue_boot
-
-echo "Create 'pmos_shell' script"
-{
-	echo "#!/bin/sh"
-	echo "sh"
-} >/usr/bin/pmos_shell
-chmod +x /usr/bin/pmos_shell
-
-echo "Create 'pmos_fail_halt_boot' script"
-{
-	echo "#!/bin/sh"
-	echo '. /init_functions.sh'
-	echo "fail_halt_boot"
-} >/usr/bin/pmos_fail_halt_boot
-chmod +x /usr/bin/pmos_fail_halt_boot
-
-echo "Start the telnet daemon"
-{
-	echo "#!/bin/sh"
-	echo "echo \"Type 'pmos_continue_boot' to continue booting:\""
-	echo "sh"
-} >/telnet_connect.sh
-chmod +x /telnet_connect.sh
-
-host_ip="${unudhcpd_host_ip:-172.16.42.1}"
-telnetd -b "${host_ip}:${TELNET_PORT}" -l /telnet_connect.sh
-
-# mount pstore, if possible
-if [ -d /sys/fs/pstore ]; then
-	mount -t pstore pstore /sys/fs/pstore || true
-fi
-# mount debugfs - very helpful for debugging
-mount -t debugfs none /sys/kernel/debug || true
-# make a symlink like Android recoveries do
-ln -s /sys/kernel/debug /d
-
-echo "---"
-echo "WARNING: debug-shell is active on ${host_ip}:${TELNET_PORT}."
-echo "This is a security hole! Only use it for debugging, and"
-echo "uninstall the debug-shell hook afterwards!"
-echo "You can expose storage devices using 'setup_usb_storage /dev/DEVICE'"
-echo "---"
-
-pmos_shell
-
-# Show "Loading" splash again when continuing
-show_splash "Loading..."
diff --git a/main/postmarketos-mkinitfs-hook-debug-shell/APKBUILD b/main/postmarketos-mkinitfs-hook-debug-shell/APKBUILD
index 46d017c2261088e0f6061d9a1214d4f754b5101d..2569235d6ab123540d99cc4259e41739b904839c 100644
--- a/main/postmarketos-mkinitfs-hook-debug-shell/APKBUILD
+++ b/main/postmarketos-mkinitfs-hook-debug-shell/APKBUILD
@@ -1,17 +1,15 @@
 pkgname=postmarketos-mkinitfs-hook-debug-shell
-pkgver=0.6.0
+pkgver=0.7.0
 pkgrel=0
 pkgdesc="Root shell in the initramfs (security hole, for debugging only)"
 url="https://postmarketos.org"
 depends="postmarketos-mkinitfs devicepkg-utils fbdebug evtest linuxconsoletools reboot-mode libinput libinput-tools"
-source="20-debug-shell.sh 20-debug-shell.files setup_usb_storage.sh"
+source="20-debug-shell.files setup_usb_storage.sh"
 arch="noarch"
 license="GPL2"
 options="!check"
 
 package() {
-	install -Dm644 "$srcdir"/20-debug-shell.sh \
-		"$pkgdir"/usr/share/mkinitfs/hooks/20-debug-shell.sh
 	install -Dm644 "$srcdir"/20-debug-shell.files \
 		"$pkgdir"/usr/share/mkinitfs/files/20-debug-shell.files
 	install -Dm755 "$srcdir"/setup_usb_storage.sh \
@@ -19,7 +17,6 @@ package() {
 }
 
 sha512sums="
-add95b3fa64b804fd386298406fbc55489e3e122d70f40e45627a450d299e148a346a4a147c2b28e0110ecef14e9e0b62ac75a0bd2d39295c2407ebb5e93708c  20-debug-shell.sh
 a739bc47d905d189edb26d9ebfd062023720fefdaab27207471c16d53a9c12ea8b81092a1047d8f2300e42ba500bdf6c5a3343aca55aab5bf8e84d68eb5680ab  20-debug-shell.files
 75d485c2e9f352cfd717b7a92753a9dfc4a72526a44bcbb784eacb4ef9011072b3ffa1c42a317c0940598cc076fb6c61676c440e5b188378b19ca08d882c1338  setup_usb_storage.sh
 "