The Raspberry Pi 4B is missing a power switch. This is very frustrating when using it for embedded data-logging applications because unclean shutdowns can corrupt the flash memory. And corrupted writes to an exfat partition on a USB stick might corrupt the entire drive!
Thankfully, the gpio-shutdown dtoverlay in /boot/config.txt lets you configure a GPIO pin to be used as a shutdown switch. Unfortunately, in the Lite version of Raspberry Pi OS, the directions given in /boot/overlays/README are out of date! So here's what I did to enable a power switch on a GPIO.
The Process
First, get a button like this one: https://www.pishop.us/product/squid-button/. Connect it between pins 39 and 40 on the 40-pin header. If you hold the board so the GPIO header is on top, these will be the two pins in the last column on the right side.
Second, add the following line to /boot/config.txt:
dtoverlay=gpio-shutdown,gpio_pin=21
Third, add the following line to /etc/console-setup/remap.inc:
keycode 116 = KeyboardSignal
Fourth, create a link from /etc/systemd/system/kbrequest.target to /lib/systemd/system/poweroff.target:
$ sudo ln -s /lib/systemd/system/poweroff.target /etc/systemd/system/kbrequest.target
Finally, reboot. When the system has started back up, you can push the GPIO button to request an orderly shutdown.
What is this doing?
The gpio-shutdown module tells the kernel to pretend that your GPIO button is a power key on a keyboard. In the desktop version of Raspberry Pi OS, this is intercepted by systemd-logind and the system shuts down. But Raspberry Pi OS Lite doesn't use systemd-logind, so we have to use another method.
The remap.inc entry modifies the kernel keymap so that the power key sends a SIGWINCH (window changed signal). This is caught by systemd, which launches kbrequest.target whenever it gets a SIGWINCH. We linked kbrequest.target to the poweroff target, so the system shuts down.
Unfortunately, this is a blunt tool. Anything else that sends a SIGWINCH will also shut down the system. By default, alt+up arrow sends a SIGWINCH. And there may be others lurking that I haven't found yet. But it will work fine for an embedded system with no keyboard and only tested software running on it.
Alternatives
It's also possible to write a daemon that monitors the GPIO pins directly and can run "halt" when the button is pressed. This might be useful if you want to do some other work before shutting down.
Instead of linking kbrequest.target to poweroff.target, you can also write your own kbrequest.target to perform an arbitrary action.