From 4bfbb802227841a6d807053707938b799e1f79b1 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 13:58:48 -0500 Subject: [PATCH 01/28] refactor: put shutdown scripts in own files --- shutdowncheck.sh | 57 ++++++++++++++++ shutdowncheckOpenElec.sh | 50 ++++++++++++++ shutdownchecksetup.sh | 119 ++-------------------------------- shutdownchecksetupOpenElec.sh | 51 +-------------- shutdownirq.py | 53 +++++++++++++++ 5 files changed, 169 insertions(+), 161 deletions(-) create mode 100755 shutdowncheck.sh create mode 100755 shutdowncheckOpenElec.sh create mode 100755 shutdownirq.py diff --git a/shutdowncheck.sh b/shutdowncheck.sh new file mode 100755 index 0000000..ce52bbe --- /dev/null +++ b/shutdowncheck.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# ATXRaspi/MightyHat interrupt based shutdown/reboot script +# Script by Felix Rusu + +#This is GPIO 7 (pin 26 on the pinout diagram). +#This is an input from ATXRaspi to the Pi. +#When button is held for ~3 seconds, this pin will become HIGH signalling to this script to poweroff the Pi. +SHUTDOWN=7 +REBOOTPULSEMINIMUM=200 #reboot pulse signal should be at least this long +REBOOTPULSEMAXIMUM=600 #reboot pulse signal should be at most this long +echo "$SHUTDOWN" > /sys/class/gpio/export +echo "in" > /sys/class/gpio/gpio$SHUTDOWN/direction +#Added reboot feature (with ATXRaspi R2.6 (or ATXRaspi 2.5 with blue dot on chip) +#Hold ATXRaspi button for at least 500ms but no more than 2000ms and a reboot HIGH pulse of 500ms length will be issued +#This is GPIO 8 (pin 24 on the pinout diagram). +#This is an output from Pi to ATXRaspi and signals that the Pi has booted. +#This pin is asserted HIGH as soon as this script runs (by writing "1" to /sys/class/gpio/gpio8/value) +BOOT=8 +echo "$BOOT" > /sys/class/gpio/export +echo "out" > /sys/class/gpio/gpio$BOOT/direction +echo "1" > /sys/class/gpio/gpio$BOOT/value + +echo -e "\n==========================================================================================" +echo " ATXRaspi shutdown POLLING script started: asserted pins ($SHUTDOWN=input,LOW; $BOOT=output,HIGH)" +echo " Waiting for GPIO$SHUTDOWN to become HIGH (short HIGH pulse=REBOOT, long HIGH=SHUTDOWN)..." +echo "==========================================================================================" + +#This loop continuously checks if the shutdown button was pressed on ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. +#It sleeps as long as that has not happened. +while [ 1 ]; do + shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) + if [ $shutdownSignal = 0 ]; then + /bin/sleep 0.2 + else + pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) + while [ $shutdownSignal = 1 ]; do + /bin/sleep 0.02 + if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then + echo -e "\n=====================================================================================" + echo " SHUTDOWN request from GPIO", SHUTDOWN, ", halting Rpi ..." + echo "=====================================================================================" + sudo poweroff + exit + fi + shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) + done + #pulse went LOW, check if it was long enough, and trigger reboot + if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then + echo -e "\n=====================================================================================" + echo " REBOOT request from GPIO", SHUTDOWN, ", recycling Rpi ..." + echo "=====================================================================================" + sudo reboot + exit + fi + fi +done diff --git a/shutdowncheckOpenElec.sh b/shutdowncheckOpenElec.sh new file mode 100755 index 0000000..cfbd582 --- /dev/null +++ b/shutdowncheckOpenElec.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +#This is GPIO 7 (pin 26 on the pinout diagram). +#This is an input from ATXRaspi to the Pi. +#When button is held for ~3 seconds, this pin will become HIGH signalling to this script to poweroff the Pi. +SHUTDOWN=7 +REBOOTPULSEMINIMUM=200 #reboot pulse signal should be at least this long +REBOOTPULSEMAXIMUM=600 #reboot pulse signal should be at most this long +echo "$SHUTDOWN" > /sys/class/gpio/export +echo "in" > /sys/class/gpio/gpio$SHUTDOWN/direction + +#Added reboot feature (with ATXRaspi R2.6 (or ATXRaspi 2.5 with blue dot on chip) +#Hold ATXRaspi button for at least 500ms but no more than 2000ms and a reboot HIGH pulse of 500ms length will be issued + +#This is GPIO 8 (pin 24 on the pinout diagram). +#This is an output from Pi to ATXRaspi and signals that the Pi has booted. +#This pin is asserted HIGH as soon as this script runs (by writing "1" to /sys/class/gpio/gpio8/value) +BOOT=8 +echo "$BOOT" > /sys/class/gpio/export +echo "out" > /sys/class/gpio/gpio$BOOT/direction +echo "1" > /sys/class/gpio/gpio$BOOT/value + +echo "ATXRaspi shutdown script started: asserted pins ($SHUTDOWN=input,LOW; $BOOT=output,HIGH). Waiting for GPIO$SHUTDOWN to become HIGH..." + +#This loop continuously checks if the shutdown button was pressed on ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. +#It sleeps as long as that has not happened. +while [ 1 ]; do + shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) + if [ $shutdownSignal = 0 ]; then + /bin/sleep 0.2 + else + pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) + while [ $shutdownSignal = 1 ]; do + /bin/sleep 0.02 + if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then + echo "ATXRaspi triggered a shutdown signal, halting Rpi ... " + poweroff + exit + fi + shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) + done + #pulse went LOW, check if it was long enough, and trigger reboot + if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then + echo "ATXRaspi triggered a reboot signal, recycling Rpi ... " + reboot + exit + fi + fi +done + diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index 67dd567..c79d83f 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -4,126 +4,21 @@ OPTION=$(whiptail --title "ATXRaspi/MightyHat shutdown/reboot script setup" --me "1" "Install INTERRUPT based script /etc/shutdownirq.py (recommended)" \ "2" "Install POLLING based script /etc/shutdowncheck.sh (classic)" \ "3" "Disable any existing shutdown script" 3>&1 1>&2 2>&3) - + exitstatus=$? if [ $exitstatus = 0 ]; then sudo sed -e '/shutdown/ s/^#*/#/' -i /etc/rc.local if [ $OPTION = 1 ]; then - echo '#!/usr/bin/python -# ATXRaspi/MightyHat interrupt based shutdown/reboot script -# Script by Tony Pottier, Felix Rusu -import RPi.GPIO as GPIO -import os -import sys -import time - -GPIO.setmode(GPIO.BCM) - -pulseStart = 0.0 -REBOOTPULSEMINIMUM = 0.2 #reboot pulse signal should be at least this long (seconds) -REBOOTPULSEMAXIMUM = 1.0 #reboot pulse signal should be at most this long (seconds) -SHUTDOWN = 7 #GPIO used for shutdown signal -BOOT = 8 #GPIO used for boot signal - -# Set up GPIO 8 and write that the PI has booted up -GPIO.setup(BOOT, GPIO.OUT, initial=GPIO.HIGH) - -# Set up GPIO 7 as interrupt for the shutdown signal to go HIGH -GPIO.setup(SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) - -print("\n==========================================================================================") -print(" ATXRaspi shutdown IRQ script started: asserted pins (",SHUTDOWN, "=input,LOW; ",BOOT,"=output,HIGH)") -print(" Waiting for GPIO", SHUTDOWN, "to become HIGH (short HIGH pulse=REBOOT, long HIGH=SHUTDOWN)...") -print("==========================================================================================") -try: - while True: - GPIO.wait_for_edge(SHUTDOWN, GPIO.RISING) - shutdownSignal = GPIO.input(SHUTDOWN) - pulseStart = time.time() #register time at which the button was pressed - while shutdownSignal: - time.sleep(0.2) - if(time.time() - pulseStart >= REBOOTPULSEMAXIMUM): - print("\n=====================================================================================") - print(" SHUTDOWN request from GPIO", SHUTDOWN, ", halting Rpi ...") - print("=====================================================================================") - os.system("sudo poweroff") - sys.exit() - shutdownSignal = GPIO.input(SHUTDOWN) - if time.time() - pulseStart >= REBOOTPULSEMINIMUM: - print("\n=====================================================================================") - print(" REBOOT request from GPIO", SHUTDOWN, ", recycling Rpi ...") - print("=====================================================================================") - os.system("sudo reboot") - sys.exit() - if GPIO.input(SHUTDOWN): #before looping we must make sure the shutdown signal went low - GPIO.wait_for_edge(SHUTDOWN, GPIO.FALLING) -except: - pass -finally: - GPIO.cleanup()' > /etc/shutdownirq.py + curl -fsSLo /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdownirq.py + sudo chmod +x /etc/shutdownirq.py sudo sed -i '$ i python /etc/shutdownirq.py &' /etc/rc.local elif [ $OPTION = 2 ]; then - echo '#!/bin/bash -# ATXRaspi/MightyHat interrupt based shutdown/reboot script -# Script by Felix Rusu - -#This is GPIO 7 (pin 26 on the pinout diagram). -#This is an input from ATXRaspi to the Pi. -#When button is held for ~3 seconds, this pin will become HIGH signalling to this script to poweroff the Pi. -SHUTDOWN=7 -REBOOTPULSEMINIMUM=200 #reboot pulse signal should be at least this long -REBOOTPULSEMAXIMUM=600 #reboot pulse signal should be at most this long -echo "$SHUTDOWN" > /sys/class/gpio/export -echo "in" > /sys/class/gpio/gpio$SHUTDOWN/direction -#Added reboot feature (with ATXRaspi R2.6 (or ATXRaspi 2.5 with blue dot on chip) -#Hold ATXRaspi button for at least 500ms but no more than 2000ms and a reboot HIGH pulse of 500ms length will be issued -#This is GPIO 8 (pin 24 on the pinout diagram). -#This is an output from Pi to ATXRaspi and signals that the Pi has booted. -#This pin is asserted HIGH as soon as this script runs (by writing "1" to /sys/class/gpio/gpio8/value) -BOOT=8 -echo "$BOOT" > /sys/class/gpio/export -echo "out" > /sys/class/gpio/gpio$BOOT/direction -echo "1" > /sys/class/gpio/gpio$BOOT/value - -echo -e "\n==========================================================================================" -echo " ATXRaspi shutdown POLLING script started: asserted pins ($SHUTDOWN=input,LOW; $BOOT=output,HIGH)" -echo " Waiting for GPIO$SHUTDOWN to become HIGH (short HIGH pulse=REBOOT, long HIGH=SHUTDOWN)..." -echo "==========================================================================================" - -#This loop continuously checks if the shutdown button was pressed on ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. -#It sleeps as long as that has not happened. -while [ 1 ]; do - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - if [ $shutdownSignal = 0 ]; then - /bin/sleep 0.2 - else - pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) - while [ $shutdownSignal = 1 ]; do - /bin/sleep 0.02 - if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then - echo -e "\n=====================================================================================" - echo " SHUTDOWN request from GPIO", SHUTDOWN, ", halting Rpi ..." - echo "=====================================================================================" - sudo poweroff - exit - fi - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - done - #pulse went LOW, check if it was long enough, and trigger reboot - if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then - echo -e "\n=====================================================================================" - echo " REBOOT request from GPIO", SHUTDOWN, ", recycling Rpi ..." - echo "=====================================================================================" - sudo reboot - exit + curl -fsSLo /etc/shutdowncheck.sh https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheck.sh + sudo chmod +x /etc/shutdowncheck.sh + sudo sed -i '$ i /etc/shutdowncheck.sh &' /etc/rc.local fi - fi -done' > /etc/shutdowncheck.sh -sudo chmod +x /etc/shutdowncheck.sh -sudo sed -i '$ i /etc/shutdowncheck.sh &' /etc/rc.local - fi - + echo "You chose option" $OPTION ": All done!" else echo "Shutdown/Reboot script setup was aborted." diff --git a/shutdownchecksetupOpenElec.sh b/shutdownchecksetupOpenElec.sh index ff8c728..341da40 100644 --- a/shutdownchecksetupOpenElec.sh +++ b/shutdownchecksetupOpenElec.sh @@ -1,56 +1,9 @@ #!/bin/sh -echo '#!/bin/bash - -#This is GPIO 7 (pin 26 on the pinout diagram). -#This is an input from ATXRaspi to the Pi. -#When button is held for ~3 seconds, this pin will become HIGH signalling to this script to poweroff the Pi. -SHUTDOWN=7 -REBOOTPULSEMINIMUM=200 #reboot pulse signal should be at least this long -REBOOTPULSEMAXIMUM=600 #reboot pulse signal should be at most this long -echo "$SHUTDOWN" > /sys/class/gpio/export -echo "in" > /sys/class/gpio/gpio$SHUTDOWN/direction - -#Added reboot feature (with ATXRaspi R2.6 (or ATXRaspi 2.5 with blue dot on chip) -#Hold ATXRaspi button for at least 500ms but no more than 2000ms and a reboot HIGH pulse of 500ms length will be issued - -#This is GPIO 8 (pin 24 on the pinout diagram). -#This is an output from Pi to ATXRaspi and signals that the Pi has booted. -#This pin is asserted HIGH as soon as this script runs (by writing "1" to /sys/class/gpio/gpio8/value) -BOOT=8 -echo "$BOOT" > /sys/class/gpio/export -echo "out" > /sys/class/gpio/gpio$BOOT/direction -echo "1" > /sys/class/gpio/gpio$BOOT/value - -echo "ATXRaspi shutdown script started: asserted pins ($SHUTDOWN=input,LOW; $BOOT=output,HIGH). Waiting for GPIO$SHUTDOWN to become HIGH..." -#This loop continuously checks if the shutdown button was pressed on ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. -#It sleeps as long as that has not happened. -while [ 1 ]; do - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - if [ $shutdownSignal = 0 ]; then - /bin/sleep 0.2 - else - pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) - while [ $shutdownSignal = 1 ]; do - /bin/sleep 0.02 - if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then - echo "ATXRaspi triggered a shutdown signal, halting Rpi ... " - poweroff - exit - fi - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - done - #pulse went LOW, check if it was long enough, and trigger reboot - if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then - echo "ATXRaspi triggered a reboot signal, recycling Rpi ... " - reboot - exit - fi - fi -done' > /storage/.config/shutdowncheck.sh +curl -fsSlo /storage/.config/shutdowncheck.sh /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheckOpenElec.sh echo '#!/bin/bash ( /storage/.config/shutdowncheck.sh )&' > /storage/.config/autostart.sh chmod 777 /storage/.config/autostart.sh -chmod 777 /storage/.config/shutdowncheck.sh \ No newline at end of file +chmod 777 /storage/.config/shutdowncheck.sh diff --git a/shutdownirq.py b/shutdownirq.py new file mode 100755 index 0000000..2f461f5 --- /dev/null +++ b/shutdownirq.py @@ -0,0 +1,53 @@ +#!/usr/bin/python + +# ATXRaspi/MightyHat interrupt based shutdown/reboot script +# Script by Tony Pottier, Felix Rusu +import RPi.GPIO as GPIO +import os +import sys +import time + +GPIO.setmode(GPIO.BCM) + +pulseStart = 0.0 +REBOOTPULSEMINIMUM = 0.2 #reboot pulse signal should be at least this long (seconds) +REBOOTPULSEMAXIMUM = 1.0 #reboot pulse signal should be at most this long (seconds) +SHUTDOWN = 7 #GPIO used for shutdown signal +BOOT = 8 #GPIO used for boot signal + +# Set up GPIO 8 and write that the PI has booted up +GPIO.setup(BOOT, GPIO.OUT, initial=GPIO.HIGH) + +# Set up GPIO 7 as interrupt for the shutdown signal to go HIGH +GPIO.setup(SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + +print("\n==========================================================================================") +print(" ATXRaspi shutdown IRQ script started: asserted pins (",SHUTDOWN, "=input,LOW; ",BOOT,"=output,HIGH)") +print(" Waiting for GPIO", SHUTDOWN, "to become HIGH (short HIGH pulse=REBOOT, long HIGH=SHUTDOWN)...") +print("==========================================================================================") +try: + while True: + GPIO.wait_for_edge(SHUTDOWN, GPIO.RISING) + shutdownSignal = GPIO.input(SHUTDOWN) + pulseStart = time.time() #register time at which the button was pressed + while shutdownSignal: + time.sleep(0.2) + if(time.time() - pulseStart >= REBOOTPULSEMAXIMUM): + print("\n=====================================================================================") + print(" SHUTDOWN request from GPIO", SHUTDOWN, ", halting Rpi ...") + print("=====================================================================================") + os.system("sudo poweroff") + sys.exit() + shutdownSignal = GPIO.input(SHUTDOWN) + if time.time() - pulseStart >= REBOOTPULSEMINIMUM: + print("\n=====================================================================================") + print(" REBOOT request from GPIO", SHUTDOWN, ", recycling Rpi ...") + print("=====================================================================================") + os.system("sudo reboot") + sys.exit() + if GPIO.input(SHUTDOWN): #before looping we must make sure the shutdown signal went low + GPIO.wait_for_edge(SHUTDOWN, GPIO.FALLING) +except: + pass +finally: + GPIO.cleanup() From c50b14e1575f473382f75e12fd3f929279ff3802 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 14:05:23 -0500 Subject: [PATCH 02/28] chore: use (more-)portable shebangs --- shutdowncheck.sh | 2 +- shutdowncheckOpenElec.sh | 2 +- shutdownchecksetup.sh | 2 +- shutdownchecksetupOpenElec.sh | 2 +- shutdownirq.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/shutdowncheck.sh b/shutdowncheck.sh index ce52bbe..bcd595f 100755 --- a/shutdowncheck.sh +++ b/shutdowncheck.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # ATXRaspi/MightyHat interrupt based shutdown/reboot script # Script by Felix Rusu diff --git a/shutdowncheckOpenElec.sh b/shutdowncheckOpenElec.sh index cfbd582..9775b11 100755 --- a/shutdowncheckOpenElec.sh +++ b/shutdowncheckOpenElec.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh #This is GPIO 7 (pin 26 on the pinout diagram). #This is an input from ATXRaspi to the Pi. diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index c79d83f..49d6c6d 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh OPTION=$(whiptail --title "ATXRaspi/MightyHat shutdown/reboot script setup" --menu "\nChoose your script type option below:\n\n(Note: changes require reboot to take effect)" 15 78 4 \ "1" "Install INTERRUPT based script /etc/shutdownirq.py (recommended)" \ diff --git a/shutdownchecksetupOpenElec.sh b/shutdownchecksetupOpenElec.sh index 341da40..84140da 100644 --- a/shutdownchecksetupOpenElec.sh +++ b/shutdownchecksetupOpenElec.sh @@ -1,7 +1,7 @@ #!/bin/sh curl -fsSlo /storage/.config/shutdowncheck.sh /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheckOpenElec.sh -echo '#!/bin/bash +echo '#!/bin/sh ( /storage/.config/shutdowncheck.sh )&' > /storage/.config/autostart.sh diff --git a/shutdownirq.py b/shutdownirq.py index 2f461f5..c6bef1c 100755 --- a/shutdownirq.py +++ b/shutdownirq.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # ATXRaspi/MightyHat interrupt based shutdown/reboot script # Script by Tony Pottier, Felix Rusu From d04e04ff2278c9c815e88b88dfcfa557282ad450 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 14:10:52 -0500 Subject: [PATCH 03/28] chore: fix shellcheck violations --- shutdowncheck.sh | 10 +++++----- shutdowncheckOpenElec.sh | 10 +++++----- shutdownchecksetup.sh | 23 +++++++++++++---------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/shutdowncheck.sh b/shutdowncheck.sh index bcd595f..b6e5d7b 100755 --- a/shutdowncheck.sh +++ b/shutdowncheck.sh @@ -28,15 +28,15 @@ echo "========================================================================== #This loop continuously checks if the shutdown button was pressed on ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. #It sleeps as long as that has not happened. -while [ 1 ]; do +while true; do shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - if [ $shutdownSignal = 0 ]; then + if [ "$shutdownSignal" = 0 ]; then /bin/sleep 0.2 else pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) - while [ $shutdownSignal = 1 ]; do + while [ "$shutdownSignal" = 1 ]; do /bin/sleep 0.02 - if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then + if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then echo -e "\n=====================================================================================" echo " SHUTDOWN request from GPIO", SHUTDOWN, ", halting Rpi ..." echo "=====================================================================================" @@ -46,7 +46,7 @@ while [ 1 ]; do shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) done #pulse went LOW, check if it was long enough, and trigger reboot - if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then + if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then echo -e "\n=====================================================================================" echo " REBOOT request from GPIO", SHUTDOWN, ", recycling Rpi ..." echo "=====================================================================================" diff --git a/shutdowncheckOpenElec.sh b/shutdowncheckOpenElec.sh index 9775b11..d9ff2ac 100755 --- a/shutdowncheckOpenElec.sh +++ b/shutdowncheckOpenElec.sh @@ -24,15 +24,15 @@ echo "ATXRaspi shutdown script started: asserted pins ($SHUTDOWN=input,LOW; $BOO #This loop continuously checks if the shutdown button was pressed on ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. #It sleeps as long as that has not happened. -while [ 1 ]; do +while true; do shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - if [ $shutdownSignal = 0 ]; then + if [ "$shutdownSignal" = 0 ]; then /bin/sleep 0.2 else pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) - while [ $shutdownSignal = 1 ]; do + while [ "$shutdownSignal" = 1 ]; do /bin/sleep 0.02 - if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then + if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then echo "ATXRaspi triggered a shutdown signal, halting Rpi ... " poweroff exit @@ -40,7 +40,7 @@ while [ 1 ]; do shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) done #pulse went LOW, check if it was long enough, and trigger reboot - if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then + if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then echo "ATXRaspi triggered a reboot signal, recycling Rpi ... " reboot exit diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index 49d6c6d..1998f2c 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -9,17 +9,20 @@ exitstatus=$? if [ $exitstatus = 0 ]; then sudo sed -e '/shutdown/ s/^#*/#/' -i /etc/rc.local - if [ $OPTION = 1 ]; then - curl -fsSLo /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdownirq.py - sudo chmod +x /etc/shutdownirq.py - sudo sed -i '$ i python /etc/shutdownirq.py &' /etc/rc.local - elif [ $OPTION = 2 ]; then - curl -fsSLo /etc/shutdowncheck.sh https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheck.sh - sudo chmod +x /etc/shutdowncheck.sh - sudo sed -i '$ i /etc/shutdowncheck.sh &' /etc/rc.local - fi + case "$OPTION" in + 1) + curl -fsSLo /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdownirq.py + sudo chmod +x /etc/shutdownirq.py + sudo sed -i '$ i python /etc/shutdownirq.py &' /etc/rc.local + ;; + 2) + curl -fsSLo /etc/shutdowncheck.sh https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheck.sh + sudo chmod +x /etc/shutdowncheck.sh + sudo sed -i '$ i /etc/shutdowncheck.sh &' /etc/rc.local + ;; + esac - echo "You chose option" $OPTION ": All done!" + echo "You chose option ${OPTION}: All done!" else echo "Shutdown/Reboot script setup was aborted." fi From a7920d0bec7a1534e75e15ede11ef1bc37bd9a01 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 14:30:51 -0500 Subject: [PATCH 04/28] feat(installers): use sudo only if necessary by detecting whether the script in question is already running as root. --- shutdownchecksetup.sh | 27 ++++++++++++++++++++------- shutdownchecksetupOpenElec.sh | 17 ++++++++++++++++- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index 1998f2c..2a697fd 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -1,5 +1,18 @@ #!/bin/sh +# We provide a fallback in case `EUID` is not set; silence shellcheck violation +# warning that "In POSIX sh, EUID is undefined" +# shellcheck disable=SC3028 +if [ "${EUID:-$(id -u 2>/dev/null || :)}" != 0 ]; then + run_as_root() { + sudo "$@" + } +else + run_as_root() { + "$@" + } +fi + OPTION=$(whiptail --title "ATXRaspi/MightyHat shutdown/reboot script setup" --menu "\nChoose your script type option below:\n\n(Note: changes require reboot to take effect)" 15 78 4 \ "1" "Install INTERRUPT based script /etc/shutdownirq.py (recommended)" \ "2" "Install POLLING based script /etc/shutdowncheck.sh (classic)" \ @@ -7,18 +20,18 @@ OPTION=$(whiptail --title "ATXRaspi/MightyHat shutdown/reboot script setup" --me exitstatus=$? if [ $exitstatus = 0 ]; then - sudo sed -e '/shutdown/ s/^#*/#/' -i /etc/rc.local + run_as_root sed -e '/shutdown/ s/^#*/#/' -i /etc/rc.local case "$OPTION" in 1) - curl -fsSLo /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdownirq.py - sudo chmod +x /etc/shutdownirq.py - sudo sed -i '$ i python /etc/shutdownirq.py &' /etc/rc.local + run_as_root curl -fsSLo /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdownirq.py + run_as_root chmod +x /etc/shutdownirq.py + run_as_root sed -i '$ i python /etc/shutdownirq.py &' /etc/rc.local ;; 2) - curl -fsSLo /etc/shutdowncheck.sh https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheck.sh - sudo chmod +x /etc/shutdowncheck.sh - sudo sed -i '$ i /etc/shutdowncheck.sh &' /etc/rc.local + run_as_root -fsSLo /etc/shutdowncheck.sh https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheck.sh + run_as_root chmod +x /etc/shutdowncheck.sh + run_as_root sed -i '$ i /etc/shutdowncheck.sh &' /etc/rc.local ;; esac diff --git a/shutdownchecksetupOpenElec.sh b/shutdownchecksetupOpenElec.sh index 84140da..8667bc0 100644 --- a/shutdownchecksetupOpenElec.sh +++ b/shutdownchecksetupOpenElec.sh @@ -1,9 +1,24 @@ #!/bin/sh -curl -fsSlo /storage/.config/shutdowncheck.sh /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheckOpenElec.sh +# We provide a fallback in case `EUID` is not set; silence shellcheck violation +# warning that "In POSIX sh, EUID is undefined" +# shellcheck disable=SC3028 +if [ "${EUID:-$(id -u 2>/dev/null || :)}" != 0 ]; then + run_as_root() { + sudo "$@" + } +else + run_as_root() { + "$@" + } +fi + +run_as_root curl -fsSlo /storage/.config/shutdowncheck.sh /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheckOpenElec.sh +run_as_root "${SHELL:-/bin/sh}" "-$-" -c " echo '#!/bin/sh ( /storage/.config/shutdowncheck.sh )&' > /storage/.config/autostart.sh +" chmod 777 /storage/.config/autostart.sh chmod 777 /storage/.config/shutdowncheck.sh From f0a84ffcddb9187100694d4b4996a20e8c0266c4 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 14:31:40 -0500 Subject: [PATCH 05/28] fix: do not assume `sleep` is `/bin/sleep` --- shutdowncheck.sh | 4 ++-- shutdowncheckOpenElec.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/shutdowncheck.sh b/shutdowncheck.sh index b6e5d7b..265f4ee 100755 --- a/shutdowncheck.sh +++ b/shutdowncheck.sh @@ -31,11 +31,11 @@ echo "========================================================================== while true; do shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) if [ "$shutdownSignal" = 0 ]; then - /bin/sleep 0.2 + sleep 0.2 else pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) while [ "$shutdownSignal" = 1 ]; do - /bin/sleep 0.02 + sleep 0.02 if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then echo -e "\n=====================================================================================" echo " SHUTDOWN request from GPIO", SHUTDOWN, ", halting Rpi ..." diff --git a/shutdowncheckOpenElec.sh b/shutdowncheckOpenElec.sh index d9ff2ac..aa9ca46 100755 --- a/shutdowncheckOpenElec.sh +++ b/shutdowncheckOpenElec.sh @@ -27,11 +27,11 @@ echo "ATXRaspi shutdown script started: asserted pins ($SHUTDOWN=input,LOW; $BOO while true; do shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) if [ "$shutdownSignal" = 0 ]; then - /bin/sleep 0.2 + sleep 0.2 else pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) while [ "$shutdownSignal" = 1 ]; do - /bin/sleep 0.02 + sleep 0.02 if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then echo "ATXRaspi triggered a shutdown signal, halting Rpi ... " poweroff From a321566b102568cead2970de6f088286ec4f72bb Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 14:32:01 -0500 Subject: [PATCH 06/28] fix: remove unnecessary `sudo` uses --- shutdowncheck.sh | 4 ++-- shutdownirq.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/shutdowncheck.sh b/shutdowncheck.sh index 265f4ee..7ea9c18 100755 --- a/shutdowncheck.sh +++ b/shutdowncheck.sh @@ -40,7 +40,7 @@ while true; do echo -e "\n=====================================================================================" echo " SHUTDOWN request from GPIO", SHUTDOWN, ", halting Rpi ..." echo "=====================================================================================" - sudo poweroff + poweroff exit fi shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) @@ -50,7 +50,7 @@ while true; do echo -e "\n=====================================================================================" echo " REBOOT request from GPIO", SHUTDOWN, ", recycling Rpi ..." echo "=====================================================================================" - sudo reboot + reboot exit fi fi diff --git a/shutdownirq.py b/shutdownirq.py index c6bef1c..33a2bc4 100755 --- a/shutdownirq.py +++ b/shutdownirq.py @@ -36,14 +36,14 @@ print("\n=====================================================================================") print(" SHUTDOWN request from GPIO", SHUTDOWN, ", halting Rpi ...") print("=====================================================================================") - os.system("sudo poweroff") + os.system("poweroff") sys.exit() shutdownSignal = GPIO.input(SHUTDOWN) if time.time() - pulseStart >= REBOOTPULSEMINIMUM: print("\n=====================================================================================") print(" REBOOT request from GPIO", SHUTDOWN, ", recycling Rpi ...") print("=====================================================================================") - os.system("sudo reboot") + os.system("reboot") sys.exit() if GPIO.input(SHUTDOWN): #before looping we must make sure the shutdown signal went low GPIO.wait_for_edge(SHUTDOWN, GPIO.FALLING) From 20aec1b55f2082a45c98dcea90e719ad384c0bde Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 14:41:10 -0500 Subject: [PATCH 07/28] feat(shutdownchec.sh): POSIX compatibility via removing non-portable `echo -e` usage and replacing it with a reusable `header` function. --- shutdowncheck.sh | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/shutdowncheck.sh b/shutdowncheck.sh index 7ea9c18..c3a8cbc 100755 --- a/shutdowncheck.sh +++ b/shutdowncheck.sh @@ -1,8 +1,35 @@ -#!/usr/bin/env bash +#!/bin/sh # ATXRaspi/MightyHat interrupt based shutdown/reboot script # Script by Felix Rusu +header() { + if [ "$#" -lt 2 ]; then + echo 1>&2 'internal error: usage: header [...]' + return 64 # EX_USAGE + fi + + padding="${1:-0}" + shift + + if [ "$((padding+0))" != "$padding" ]; then + echo 1>&2 'internal error: padding must be an integer' + return 64 # EX_USAGE + fi + + left='' + while [ "$padding" -gt 0 ]; do + left="${left} " + padding="$((padding-1))" + done + + echo "==========================================================================================" + for line in "$@"; do + echo "${left}${line}" + done + echo "==========================================================================================" +} + #This is GPIO 7 (pin 26 on the pinout diagram). #This is an input from ATXRaspi to the Pi. #When button is held for ~3 seconds, this pin will become HIGH signalling to this script to poweroff the Pi. @@ -21,10 +48,9 @@ echo "$BOOT" > /sys/class/gpio/export echo "out" > /sys/class/gpio/gpio$BOOT/direction echo "1" > /sys/class/gpio/gpio$BOOT/value -echo -e "\n==========================================================================================" -echo " ATXRaspi shutdown POLLING script started: asserted pins ($SHUTDOWN=input,LOW; $BOOT=output,HIGH)" -echo " Waiting for GPIO$SHUTDOWN to become HIGH (short HIGH pulse=REBOOT, long HIGH=SHUTDOWN)..." -echo "==========================================================================================" +header 3 \ + "ATXRaspi shutdown POLLING script started: asserted pins ($SHUTDOWN=input,LOW; $BOOT=output,HIGH)" \ + "Waiting for GPIO$SHUTDOWN to become HIGH (short HIGH pulse=REBOOT, long HIGH=SHUTDOWN)..." #This loop continuously checks if the shutdown button was pressed on ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. #It sleeps as long as that has not happened. @@ -37,9 +63,7 @@ while true; do while [ "$shutdownSignal" = 1 ]; do sleep 0.02 if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then - echo -e "\n=====================================================================================" - echo " SHUTDOWN request from GPIO", SHUTDOWN, ", halting Rpi ..." - echo "=====================================================================================" + header 12 "SHUTDOWN request from GPIO${SHUTDOWN}, halting Rpi ..." poweroff exit fi @@ -47,9 +71,7 @@ while true; do done #pulse went LOW, check if it was long enough, and trigger reboot if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then - echo -e "\n=====================================================================================" - echo " REBOOT request from GPIO", SHUTDOWN, ", recycling Rpi ..." - echo "=====================================================================================" + header 12 "REBOOT request from GPIO${SHUTDOWN}, recycling Rpi ..." reboot exit fi From 18287f70003662702a57bd7454388e751bf428b5 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 14:46:13 -0500 Subject: [PATCH 08/28] chore: use shutdowncheck.sh on OpenELEC as `shutdowncheck.sh` is now implemented in POSIX sh. BREAKING CHANGE: diagnostic messages now use a pretty header, so any code that relies on the old message format may break. --- shutdowncheckOpenElec.sh | 51 +--------------------------------------- 1 file changed, 1 insertion(+), 50 deletions(-) mode change 100755 => 120000 shutdowncheckOpenElec.sh diff --git a/shutdowncheckOpenElec.sh b/shutdowncheckOpenElec.sh deleted file mode 100755 index aa9ca46..0000000 --- a/shutdowncheckOpenElec.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh - -#This is GPIO 7 (pin 26 on the pinout diagram). -#This is an input from ATXRaspi to the Pi. -#When button is held for ~3 seconds, this pin will become HIGH signalling to this script to poweroff the Pi. -SHUTDOWN=7 -REBOOTPULSEMINIMUM=200 #reboot pulse signal should be at least this long -REBOOTPULSEMAXIMUM=600 #reboot pulse signal should be at most this long -echo "$SHUTDOWN" > /sys/class/gpio/export -echo "in" > /sys/class/gpio/gpio$SHUTDOWN/direction - -#Added reboot feature (with ATXRaspi R2.6 (or ATXRaspi 2.5 with blue dot on chip) -#Hold ATXRaspi button for at least 500ms but no more than 2000ms and a reboot HIGH pulse of 500ms length will be issued - -#This is GPIO 8 (pin 24 on the pinout diagram). -#This is an output from Pi to ATXRaspi and signals that the Pi has booted. -#This pin is asserted HIGH as soon as this script runs (by writing "1" to /sys/class/gpio/gpio8/value) -BOOT=8 -echo "$BOOT" > /sys/class/gpio/export -echo "out" > /sys/class/gpio/gpio$BOOT/direction -echo "1" > /sys/class/gpio/gpio$BOOT/value - -echo "ATXRaspi shutdown script started: asserted pins ($SHUTDOWN=input,LOW; $BOOT=output,HIGH). Waiting for GPIO$SHUTDOWN to become HIGH..." - -#This loop continuously checks if the shutdown button was pressed on ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. -#It sleeps as long as that has not happened. -while true; do - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - if [ "$shutdownSignal" = 0 ]; then - sleep 0.2 - else - pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) - while [ "$shutdownSignal" = 1 ]; do - sleep 0.02 - if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then - echo "ATXRaspi triggered a shutdown signal, halting Rpi ... " - poweroff - exit - fi - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - done - #pulse went LOW, check if it was long enough, and trigger reboot - if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then - echo "ATXRaspi triggered a reboot signal, recycling Rpi ... " - reboot - exit - fi - fi -done - diff --git a/shutdowncheckOpenElec.sh b/shutdowncheckOpenElec.sh new file mode 120000 index 0000000..cfe9e86 --- /dev/null +++ b/shutdowncheckOpenElec.sh @@ -0,0 +1 @@ +shutdowncheck.sh \ No newline at end of file From 77d319259985d5ee7ad8f80989e9c6616d005eb8 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 14:52:53 -0500 Subject: [PATCH 09/28] chore: enable shell "strict mode" (`set -eu`) --- shutdowncheck.sh | 2 ++ shutdownchecksetup.sh | 2 ++ shutdownchecksetupOpenElec.sh | 2 ++ 3 files changed, 6 insertions(+) diff --git a/shutdowncheck.sh b/shutdowncheck.sh index c3a8cbc..0ceb507 100755 --- a/shutdowncheck.sh +++ b/shutdowncheck.sh @@ -3,6 +3,8 @@ # ATXRaspi/MightyHat interrupt based shutdown/reboot script # Script by Felix Rusu +set -eu + header() { if [ "$#" -lt 2 ]; then echo 1>&2 'internal error: usage: header [...]' diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index 2a697fd..0097949 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -eu + # We provide a fallback in case `EUID` is not set; silence shellcheck violation # warning that "In POSIX sh, EUID is undefined" # shellcheck disable=SC3028 diff --git a/shutdownchecksetupOpenElec.sh b/shutdownchecksetupOpenElec.sh index 8667bc0..e8ee9d7 100644 --- a/shutdownchecksetupOpenElec.sh +++ b/shutdownchecksetupOpenElec.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -eu + # We provide a fallback in case `EUID` is not set; silence shellcheck violation # warning that "In POSIX sh, EUID is undefined" # shellcheck disable=SC3028 From 2680a669cb3b2ab1b15e76961bcd8d49c9f0b2d3 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 15:09:28 -0500 Subject: [PATCH 10/28] feat: do not require whiptail for prompts --- shutdownchecksetup.sh | 65 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index 0097949..c7bab50 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -15,13 +15,66 @@ else } fi -OPTION=$(whiptail --title "ATXRaspi/MightyHat shutdown/reboot script setup" --menu "\nChoose your script type option below:\n\n(Note: changes require reboot to take effect)" 15 78 4 \ -"1" "Install INTERRUPT based script /etc/shutdownirq.py (recommended)" \ -"2" "Install POLLING based script /etc/shutdowncheck.sh (classic)" \ -"3" "Disable any existing shutdown script" 3>&1 1>&2 2>&3) +if command -v whiptail 1>/dev/null 2>&1; then + get_script_type() { + whiptail --title "ATXRaspi/MightyHat shutdown/reboot script setup" --menu "\nChoose your script type option below:\n\n(Note: changes require reboot to take effect)" 15 78 4 \ + "1" "Install INTERRUPT based script /etc/shutdownirq.py (recommended)" \ + "2" "Install POLLING based script /etc/shutdowncheck.sh (classic)" \ + "3" "Disable any existing shutdown script" 3>&1 1>&2 2>&3 + } +elif (help select) 1>/dev/null 2>&1; then + # Eval this, as otherwise we're liable to get a syntax error from shells that + # do not understand `select ...; do ...; done` + eval ' + get_script_type() { + echo 1>&2 "ATXRaspi/MightyHat shutdown/reboot script setup" + echo 1>&2 "Choose your script type option below (note: changes require reboot to take effect)" + + # shellcheck disable=SC3043 + local PS3="Choose your script type option: " + + # shellcheck disable=SC3008 + select CHOICE in "Install INTERRUPT based script /etc/shutdownirq.py (recommended)" \ + "Install POLLING based script /etc/shutdowncheck.sh (classic)" \ + "Disable any existing shutdown script"; do + if [ -n "${CHOICE:-}" ]; then + echo "$REPLY" + break + else + echo 1>&2 "Not a valid choice: ${REPLY}" + fi + done + } + ' +else + get_script_type() { + echo 1>&2 'ATXRaspi/MightyHat shutdown/reboot script setup' + echo 1>&2 "Choose your script type option below (note: changes require reboot to take effect)" + echo 1>&2 "\ +1) Install INTERRUPT based script /etc/shutdownirq.py (recommended) +2) Install POLLING based script /etc/shutdowncheck.sh (classic) +3) Disable any existing shutdown script" + + + while true; do + echo 1>&2 'Choose your script type option: ' + + if read -r REPLY; then + case "${REPLY:-}" in + 1|2|3) + echo "$REPLY" + return + ;; + *) + echo 1>&2 "Not a valid choice: ${REPLY}" + ;; + esac + fi + done + } +fi -exitstatus=$? -if [ $exitstatus = 0 ]; then +if OPTION="$(get_script_type)"; then run_as_root sed -e '/shutdown/ s/^#*/#/' -i /etc/rc.local case "$OPTION" in From 978f5d5627a22e7b2173ced8fe84e47673848038 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 15:18:48 -0500 Subject: [PATCH 11/28] feat(shutdownchecksetup.sh): support OpenELEC --- shutdownchecksetup.sh | 59 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index c7bab50..55159e9 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -15,6 +15,57 @@ else } fi +install_interrupt_script() { + run_as_root curl -fsSLo /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdownirq.py + run_as_root chmod +x /etc/shutdownirq.py + run_as_root sed -i '$ i python /etc/shutdownirq.py &' /etc/rc.local +} + +install_polling_script() { + dest="${1:-/etc/shutdowncheck.sh}" + run_as_root -fsSLo "$dest" https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheck.sh + run_as_root chmod +x "$dest" + run_as_root sed -i "\$ i ${dest} &" /etc/rc.local +} + +looks_like_elec_distro() { + # shellcheck disable=SC1091 + . /etc/os-release 1>/dev/null 2>&1 || : + + case "${NAME:-}" in + *ELEC) + return 0 + ;; + esac + + case "${ID:-}" in + *elec) + return 0 + ;; + esac + + # shellcheck disable=SC3028 + # *ELEC distros: + # 1. Use `root` for shell sessions, + # 2. Have the directory `/storage/.config`, and + # 3. Use a `sudo` wrapper that issues a warning about not needing sudo + # and then exits with a non-zero status. + [ "${EUID:-$(id -u 2>/dev/null || :)}" -eq 0 ] && [ -d /storage/.config ] && ! sudo true +} + +if looks_like_elec_distro; then + install_polling_script /storage/.config/shutdowncheck.sh + run_as_root "${SHELL:-/bin/sh}" "-$-" -c " +echo '#!/bin/sh +( +/storage/.config/shutdowncheck.sh +)&' > /storage/.config/autostart.sh +" + chmod 777 /storage/.config/autostart.sh + chmod 777 /storage/.config/shutdowncheck.sh + exit +fi + if command -v whiptail 1>/dev/null 2>&1; then get_script_type() { whiptail --title "ATXRaspi/MightyHat shutdown/reboot script setup" --menu "\nChoose your script type option below:\n\n(Note: changes require reboot to take effect)" 15 78 4 \ @@ -79,14 +130,10 @@ if OPTION="$(get_script_type)"; then case "$OPTION" in 1) - run_as_root curl -fsSLo /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdownirq.py - run_as_root chmod +x /etc/shutdownirq.py - run_as_root sed -i '$ i python /etc/shutdownirq.py &' /etc/rc.local + install_interrupt_script ;; 2) - run_as_root -fsSLo /etc/shutdowncheck.sh https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheck.sh - run_as_root chmod +x /etc/shutdowncheck.sh - run_as_root sed -i '$ i /etc/shutdowncheck.sh &' /etc/rc.local + install_polling_script ;; esac From 9b7df2967c2729da02b6f026608e47a2e5dc09c3 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Fri, 16 Feb 2024 15:19:21 -0500 Subject: [PATCH 12/28] chore: backcompat link for OpenELEC setup script --- shutdownchecksetupOpenElec.sh | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) mode change 100644 => 120000 shutdownchecksetupOpenElec.sh diff --git a/shutdownchecksetupOpenElec.sh b/shutdownchecksetupOpenElec.sh deleted file mode 100644 index e8ee9d7..0000000 --- a/shutdownchecksetupOpenElec.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -set -eu - -# We provide a fallback in case `EUID` is not set; silence shellcheck violation -# warning that "In POSIX sh, EUID is undefined" -# shellcheck disable=SC3028 -if [ "${EUID:-$(id -u 2>/dev/null || :)}" != 0 ]; then - run_as_root() { - sudo "$@" - } -else - run_as_root() { - "$@" - } -fi - -run_as_root curl -fsSlo /storage/.config/shutdowncheck.sh /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheckOpenElec.sh -run_as_root "${SHELL:-/bin/sh}" "-$-" -c " -echo '#!/bin/sh -( -/storage/.config/shutdowncheck.sh -)&' > /storage/.config/autostart.sh -" -chmod 777 /storage/.config/autostart.sh -chmod 777 /storage/.config/shutdowncheck.sh diff --git a/shutdownchecksetupOpenElec.sh b/shutdownchecksetupOpenElec.sh new file mode 120000 index 0000000..27c0bc0 --- /dev/null +++ b/shutdownchecksetupOpenElec.sh @@ -0,0 +1 @@ +shutdownchecksetup.sh \ No newline at end of file From bcb3b85c43ef1868d9e200a310bd279fa1d8f3c2 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Sat, 17 Feb 2024 10:24:30 -0500 Subject: [PATCH 13/28] feat: support `wget` for fetching scripts in addition to supporting `curl`. --- shutdownchecksetup.sh | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index 55159e9..7353963 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -15,15 +15,30 @@ else } fi +if command -v curl 1>/dev/null 2>&1; then + fetch() { + run_as_root curl -fsSLo "$2" "$1" + } +elif command -v wget 1>/dev/null 2>&1; then + fetch() { + run_as_root wget -o "$2" "$1" + } +else + fetch() { + echo 1>&2 "neither curl nor wget are available; cannot fetch '$1' into '$2'" + return 127 + } +fi + install_interrupt_script() { - run_as_root curl -fsSLo /etc/shutdownirq.py https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdownirq.py + fetch https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdownirq.py /etc/shutdownirq.py run_as_root chmod +x /etc/shutdownirq.py run_as_root sed -i '$ i python /etc/shutdownirq.py &' /etc/rc.local } install_polling_script() { dest="${1:-/etc/shutdowncheck.sh}" - run_as_root -fsSLo "$dest" https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheck.sh + fetch https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheck.sh "$dest" run_as_root chmod +x "$dest" run_as_root sed -i "\$ i ${dest} &" /etc/rc.local } From 33ed57f53144fa121898c1321c29f89cbd208f73 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Sat, 17 Feb 2024 10:29:28 -0500 Subject: [PATCH 14/28] feat: support specifying source URL components or the full source URL, minus script name. --- shutdownchecksetup.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index 7353963..70b926a 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -2,6 +2,11 @@ set -eu +SHUTDOWNCHECK_OWNER="${SHUTDOWNCHECK_OWNER:-LowPowerLab}" +SHUTDOWNCHECK_REPO="${SHUTDOWNCHECK_REPO:-ATX-Raspi}" +SHUTDOWNCHECK_REV="${SHUTDOWNCHECK_REV:-master}" +SHUTDOWNCHECK_BASEURL="${SHUTDOWNCHECK_BASEURL:-"https://githubusercontent.com/${SHUTDOWNCHECK_OWNER}/${SHUTDOWNCHECK_REPO}/${SHUTDOWNCHECK_REV}"}" + # We provide a fallback in case `EUID` is not set; silence shellcheck violation # warning that "In POSIX sh, EUID is undefined" # shellcheck disable=SC3028 @@ -31,14 +36,14 @@ else fi install_interrupt_script() { - fetch https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdownirq.py /etc/shutdownirq.py + fetch "${SHUTDOWNCHECK_BASEURL}/shutdownirq.py" /etc/shutdownirq.py run_as_root chmod +x /etc/shutdownirq.py run_as_root sed -i '$ i python /etc/shutdownirq.py &' /etc/rc.local } install_polling_script() { dest="${1:-/etc/shutdowncheck.sh}" - fetch https://githubusercontent.com/LowPowerLab/ATX-Raspi/master/shutdowncheck.sh "$dest" + fetch "${SHUTDOWNCHECK_BASEURL}/shutdowncheck.sh" "$dest" run_as_root chmod +x "$dest" run_as_root sed -i "\$ i ${dest} &" /etc/rc.local } From 6c9aeb9382dc42d34445983f6525d50d33aeda85 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Sat, 17 Feb 2024 10:44:39 -0500 Subject: [PATCH 15/28] fix: add `raw.` to `githubusercontent.com` --- shutdownchecksetup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index 70b926a..b6835fd 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -5,7 +5,7 @@ set -eu SHUTDOWNCHECK_OWNER="${SHUTDOWNCHECK_OWNER:-LowPowerLab}" SHUTDOWNCHECK_REPO="${SHUTDOWNCHECK_REPO:-ATX-Raspi}" SHUTDOWNCHECK_REV="${SHUTDOWNCHECK_REV:-master}" -SHUTDOWNCHECK_BASEURL="${SHUTDOWNCHECK_BASEURL:-"https://githubusercontent.com/${SHUTDOWNCHECK_OWNER}/${SHUTDOWNCHECK_REPO}/${SHUTDOWNCHECK_REV}"}" +SHUTDOWNCHECK_BASEURL="${SHUTDOWNCHECK_BASEURL:-"https://raw.githubusercontent.com/${SHUTDOWNCHECK_OWNER}/${SHUTDOWNCHECK_REPO}/${SHUTDOWNCHECK_REV}"}" # We provide a fallback in case `EUID` is not set; silence shellcheck violation # warning that "In POSIX sh, EUID is undefined" From ae20e7cbbe998b186afb6c30b32eac918ed0c84b Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Sat, 17 Feb 2024 13:39:19 -0500 Subject: [PATCH 16/28] feat: support specifying script installation path --- shutdownchecksetup.sh | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index b6835fd..5b85496 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -36,9 +36,10 @@ else fi install_interrupt_script() { - fetch "${SHUTDOWNCHECK_BASEURL}/shutdownirq.py" /etc/shutdownirq.py - run_as_root chmod +x /etc/shutdownirq.py - run_as_root sed -i '$ i python /etc/shutdownirq.py &' /etc/rc.local + dest="${1:-/etc/shutdownirq.py}" + fetch "${SHUTDOWNCHECK_BASEURL}/shutdownirq.py" "$dest" + run_as_root chmod +x "$dest" + run_as_root sed -i "$ i python ${dest} &" /etc/rc.local } install_polling_script() { @@ -74,15 +75,16 @@ looks_like_elec_distro() { } if looks_like_elec_distro; then - install_polling_script /storage/.config/shutdowncheck.sh + dest="${SHUTDOWNCHECK_POLLING_DEST:-${SHUTDOWNCHECK_DEST:-/storage/.config/shutdowncheck.sh}}" + install_polling_script "$dest" run_as_root "${SHELL:-/bin/sh}" "-$-" -c " echo '#!/bin/sh ( -/storage/.config/shutdowncheck.sh +${dest} )&' > /storage/.config/autostart.sh " chmod 777 /storage/.config/autostart.sh - chmod 777 /storage/.config/shutdowncheck.sh + chmod 777 "$dest" exit fi @@ -150,10 +152,10 @@ if OPTION="$(get_script_type)"; then case "$OPTION" in 1) - install_interrupt_script + install_interrupt_script "${SHUTDOWNCHECK_INTERRUPT_DEST:-${SHUTDOWNCHECK_DEST:-}}" ;; 2) - install_polling_script + install_polling_script "${SHUTDOWNCHECK_POLLING_DEST:-${SHUTDOWNCHECK_DEST:-}}" ;; esac From 987f71255c0ed9620083adb53deb2d80c047c2c2 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Sat, 17 Feb 2024 13:47:27 -0500 Subject: [PATCH 17/28] fix: only modify /etc/rc.local if it exists and is a regular file. --- shutdownchecksetup.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index 5b85496..617d18a 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -39,14 +39,18 @@ install_interrupt_script() { dest="${1:-/etc/shutdownirq.py}" fetch "${SHUTDOWNCHECK_BASEURL}/shutdownirq.py" "$dest" run_as_root chmod +x "$dest" - run_as_root sed -i "$ i python ${dest} &" /etc/rc.local + if [ -f /etc/rc.local ]; then + run_as_root sed -i "$ i python ${dest} &" /etc/rc.local + fi } install_polling_script() { dest="${1:-/etc/shutdowncheck.sh}" fetch "${SHUTDOWNCHECK_BASEURL}/shutdowncheck.sh" "$dest" run_as_root chmod +x "$dest" - run_as_root sed -i "\$ i ${dest} &" /etc/rc.local + if [ -f /etc/rc.local ]; then + run_as_root sed -i "\$ i ${dest} &" /etc/rc.local + fi } looks_like_elec_distro() { From 511b2e7ea1bb78981804d94bd135de94dafb1743 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Sun, 3 Mar 2024 11:20:03 -0500 Subject: [PATCH 18/28] feat: support chardev GPIO interface in `shutdowncheck.sh`, as long as the required tools from `libgpiod` are available. Additionally, exit with an explicit and informative error message in case neither the `libgpiod` tools nor the legacy sysfs GPIO interface are available. --- shutdowncheck.sh | 151 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 119 insertions(+), 32 deletions(-) diff --git a/shutdowncheck.sh b/shutdowncheck.sh index 0ceb507..8e75c30 100755 --- a/shutdowncheck.sh +++ b/shutdowncheck.sh @@ -32,50 +32,137 @@ header() { echo "==========================================================================================" } +f2msec() { + case "${1?}" in + *.*.*) + return 1 + ;; + *.*) + __f2msec_whole="${1%%.*}" + __f2msec_fractional="${1#*.}" + + __f2msec_exp="${#__f2msec_fractional}" + __f2msec_scale=1 + while [ "$__f2msec_exp" -gt 0 ]; do + __f2msec_scale="$(( __f2msec_scale * 10 ))" + __f2msec_exp="$(( __f2msec_exp - 1 ))" + done + + echo "$(( (__f2msec_whole * 1000) + ((__f2msec_fractional * 1000) / __f2msec_scale) ))" + ;; + *) + echo "$(( "$1" * 1000 ))" + ;; + esac +} + #This is GPIO 7 (pin 26 on the pinout diagram). #This is an input from ATXRaspi to the Pi. #When button is held for ~3 seconds, this pin will become HIGH signalling to this script to poweroff the Pi. SHUTDOWN=7 REBOOTPULSEMINIMUM=200 #reboot pulse signal should be at least this long REBOOTPULSEMAXIMUM=600 #reboot pulse signal should be at most this long -echo "$SHUTDOWN" > /sys/class/gpio/export -echo "in" > /sys/class/gpio/gpio$SHUTDOWN/direction + #Added reboot feature (with ATXRaspi R2.6 (or ATXRaspi 2.5 with blue dot on chip) #Hold ATXRaspi button for at least 500ms but no more than 2000ms and a reboot HIGH pulse of 500ms length will be issued #This is GPIO 8 (pin 24 on the pinout diagram). #This is an output from Pi to ATXRaspi and signals that the Pi has booted. #This pin is asserted HIGH as soon as this script runs (by writing "1" to /sys/class/gpio/gpio8/value) BOOT=8 -echo "$BOOT" > /sys/class/gpio/export -echo "out" > /sys/class/gpio/gpio$BOOT/direction -echo "1" > /sys/class/gpio/gpio$BOOT/value -header 3 \ - "ATXRaspi shutdown POLLING script started: asserted pins ($SHUTDOWN=input,LOW; $BOOT=output,HIGH)" \ - "Waiting for GPIO$SHUTDOWN to become HIGH (short HIGH pulse=REBOOT, long HIGH=SHUTDOWN)..." - -#This loop continuously checks if the shutdown button was pressed on ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. -#It sleeps as long as that has not happened. -while true; do - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - if [ "$shutdownSignal" = 0 ]; then - sleep 0.2 - else - pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) - while [ "$shutdownSignal" = 1 ]; do - sleep 0.02 - if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then - header 12 "SHUTDOWN request from GPIO${SHUTDOWN}, halting Rpi ..." - poweroff - exit - fi +CHIP="${CHIP:-/dev/gpiochip0}" + +if { command -v gpioset && command -v gpiomon ; } 1>/dev/null 2>&1; then + init_shutdown_pin() { + # NOP + : + } + + init_boot_pin() { + gpioset -z -b pull-up -t 0 -c "${CHIP?}" "${BOOT?}=1" + } + + watch_shutdown_pin() { + gpiomon -c "${CHIP?}" -F '%e %S' "${SHUTDOWN?}" | while read -r event seconds; do + case "$event" in + # Rising + 1) + pulseStart="$(f2msec "$seconds")" + ;; + # Falling + 2) + pulseEnd="$(f2msec "$seconds")" + pulseStart="${pulseStart:-"$pulseEnd"}" + pulseDuration="$(( pulseEnd - pulseStart ))" + + if [ "$pulseDuration" -gt $REBOOTPULSEMAXIMUM ]; then + header 12 "SHUTDOWN request on chip ${CHIP?} from GPIO${SHUTDOWN}, halting Rpi ..." + return + elif [ "$pulseDuration" -gt $REBOOTPULSEMINIMUM ]; then + header 12 "REBOOT request on chip ${CHIP?} from GPIO${SHUTDOWN?}, recycling Rpi ..." + reboot + return + else + unset pulseStart pulseEnd + fi + ;; + *) + echo 1>&2 "WARNING: unrecognized event '${event}' on chip ${CHIP?} for GPIO${SHUTDOWN?}; ignoring." + ;; + esac + done + } +elif [ -e /sys/class/gpio/export ]; then + init_shutdown_pin() { + echo "${SHUTDOWN?}" > /sys/class/gpio/export + echo in > "/sys/class/gpio/gpio${SHUTDOWN?}/direction" + } + + init_boot_pin() { + echo "${BOOT?}" > /sys/class/gpio/export + echo out > "/sys/class/gpio/gpio${BOOT?}/direction" + echo 1 > "/sys/class/gpio/gpio${BOOT?}/value" + } + + watch_shutdown_pin() { + #This loop continuously checks if the shutdown button was pressed on + #ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. + #It sleeps as long as that has not happened. + while true; do shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) + if [ "$shutdownSignal" = 0 ]; then + sleep 0.2 + else + pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) + while [ "$shutdownSignal" = 1 ]; do + sleep 0.02 + if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then + header 12 "SHUTDOWN request from GPIO${SHUTDOWN}, halting Rpi ..." + poweroff + return + fi + shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) + done + #pulse went LOW, check if it was long enough, and trigger reboot + if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then + header 12 "REBOOT request from GPIO${SHUTDOWN}, recycling Rpi ..." + reboot + return + fi + fi done - #pulse went LOW, check if it was long enough, and trigger reboot - if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then - header 12 "REBOOT request from GPIO${SHUTDOWN}, recycling Rpi ..." - reboot - exit - fi - fi -done + } +else + # shellcheck disable=SC2016 + echo 1>&2 'FATAL: missing `libgpiod` tools (`gpioset`, `gpiomon`), and legacy sysfs GPIO interface is unavailable; terminating.' + exit 1 +fi + +init_shutdown_pin +init_boot_pin + +header 3 \ + "ATXRaspi shutdown POLLING script started: asserted pins ($SHUTDOWN=input,LOW; $BOOT=output,HIGH)" \ + "Waiting for GPIO$SHUTDOWN to become HIGH (short HIGH pulse=REBOOT, long pulse HIGH=SHUTDOWN)..." + +watch_shutdown_pin From 722a057037c382b84fac869dc6f5aecd0976f4c0 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Tue, 25 Mar 2025 00:02:25 -0400 Subject: [PATCH 19/28] refactor(shutdownirq): support libgpiod --- shutdownirq.py | 146 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 107 insertions(+), 39 deletions(-) diff --git a/shutdownirq.py b/shutdownirq.py index 33a2bc4..0e98683 100755 --- a/shutdownirq.py +++ b/shutdownirq.py @@ -2,52 +2,120 @@ # ATXRaspi/MightyHat interrupt based shutdown/reboot script # Script by Tony Pottier, Felix Rusu -import RPi.GPIO as GPIO + import os import sys import time -GPIO.setmode(GPIO.BCM) +# Reboot pulse signal should be at least this long (seconds). +REBOOTPULSEMINIMUM = 0.2 + +# Reboot pulse signal should be at most this long (seconds). +REBOOTPULSEMAXIMUM = 1.0 -pulseStart = 0.0 -REBOOTPULSEMINIMUM = 0.2 #reboot pulse signal should be at least this long (seconds) -REBOOTPULSEMAXIMUM = 1.0 #reboot pulse signal should be at most this long (seconds) -SHUTDOWN = 7 #GPIO used for shutdown signal -BOOT = 8 #GPIO used for boot signal +# GPIO pin used for shutdown signal. +SHUTDOWN = 7 -# Set up GPIO 8 and write that the PI has booted up -GPIO.setup(BOOT, GPIO.OUT, initial=GPIO.HIGH) +# GPIO pin used for boot signal. +BOOT = 8 -# Set up GPIO 7 as interrupt for the shutdown signal to go HIGH -GPIO.setup(SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) +def diag(*msgs): + linelen = max([len(msg) for msg in msgs]) + 2 + wrapper = "=" * linelen + print(wrapper) + for msg in msgs: + print(" {0}".format(msg)) + print(wrapper) + +def announce(): + print() + diag( + "ATXRaspi shutdown IRQ script started: asserted pins ({0}=input,LOW; {1}=output,HIGH)".format(SHUTDOWN, BOOT), + "Waiting for GPIO {0} to become HIGH (short HIGH pulse=REBOOT, long HIGH pulse=SHUTDOWN)...".format(SHUTDOWN), + ) -print("\n==========================================================================================") -print(" ATXRaspi shutdown IRQ script started: asserted pins (",SHUTDOWN, "=input,LOW; ",BOOT,"=output,HIGH)") -print(" Waiting for GPIO", SHUTDOWN, "to become HIGH (short HIGH pulse=REBOOT, long HIGH=SHUTDOWN)...") -print("==========================================================================================") try: - while True: - GPIO.wait_for_edge(SHUTDOWN, GPIO.RISING) - shutdownSignal = GPIO.input(SHUTDOWN) - pulseStart = time.time() #register time at which the button was pressed - while shutdownSignal: - time.sleep(0.2) - if(time.time() - pulseStart >= REBOOTPULSEMAXIMUM): - print("\n=====================================================================================") - print(" SHUTDOWN request from GPIO", SHUTDOWN, ", halting Rpi ...") - print("=====================================================================================") - os.system("poweroff") + import gpiod + from gpiod.line import Direction, Edge, Value + have_gpiod = True +except ImportError: + have_gpiod = False + +if have_gpiod: + + CHIP = "/dev/gpiochip0" + CONSUMER = "atx-raspi" + CONFIG = { + SHUTDOWN: gpiod.LineSettings(direction=Direction.INPUT, edge_detection=Edge.BOTH), + BOOT: gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.ACTIVE) + } + + pulse_start = None + + with gpiod.request_lines(CHIP, consumer=CONSUMER, config=CONFIG) as request: + + announce() + + try: + while True: + for event in request.read_edge_events(): + if event.event_type is event.Type.RISING_EDGE: + pulse_start = time.time() + elif event.event_type is event.Type.FALLING_EDGE: + pulse_end = time.time() + + if pulse_start is None: + pulse_start = pulse_end + + pulse_duration = pulse_end - pulse_start + + if pulse_duration >= REBOOTPULSEMAXIMUM: + print() + diag("SHUTDOWN request on chip {0} from GPIO{1}, halting Rpi ...".format(CHIP, SHUTDOWN)) + os.system("poweroff") + sys.exit() + elif pulse_duration >= REBOOTPULSEMINIMUM: + print() + diag("REBOOT request on chip {0} from GPIO{1}, recycling Rpi ...".format(CHIP, SHUTDOWN)) + os.system("reboot") + sys.exit() + else: + pulse_start = None + pulse_end = None + except: + pass +else: + import RPi.GPIO as GPIO + + # Set up GPIO 8 and write that the PI has booted up + GPIO.setup(BOOT, GPIO.OUT, initial=GPIO.HIGH) + + # Set up GPIO 7 as interrupt for the shutdown signal to go HIGH + GPIO.setup(SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + + announce() + + try: + while True: + GPIO.wait_for_edge(SHUTDOWN, GPIO.RISING) + shutdown_signal = GPIO.input(SHUTDOWN) + pulse_start = time.time() # register time at which the button was pressed + while shutdown_signal: + time.sleep(0.2) + if(time.time() - pulse_start >= REBOOTPULSEMAXIMUM): + print() + diag("SHUTDOWN request from GPIO{0}, halting Rpi ...".format(SHUTDOWN)) + os.system("poweroff") + sys.exit() + shutdown_signal = GPIO.input(SHUTDOWN) + if time.time() - pulse_start >= REBOOTPULSEMINIMUM: + print() + diag("REBOOT request from GPIO{0}, recycling Rpi ...".format(SHUTDOWN)) + os.system("reboot") sys.exit() - shutdownSignal = GPIO.input(SHUTDOWN) - if time.time() - pulseStart >= REBOOTPULSEMINIMUM: - print("\n=====================================================================================") - print(" REBOOT request from GPIO", SHUTDOWN, ", recycling Rpi ...") - print("=====================================================================================") - os.system("reboot") - sys.exit() - if GPIO.input(SHUTDOWN): #before looping we must make sure the shutdown signal went low - GPIO.wait_for_edge(SHUTDOWN, GPIO.FALLING) -except: - pass -finally: - GPIO.cleanup() + if GPIO.input(SHUTDOWN): # before looping we must make sure the shutdown signal went low + GPIO.wait_for_edge(SHUTDOWN, GPIO.FALLING) + except: + pass + finally: + GPIO.cleanup() From 532ef9f86675e233522a9255b04d5608a8ffdc0c Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Tue, 25 Mar 2025 00:09:21 -0400 Subject: [PATCH 20/28] feat: ATX_RASPI_* env vars for configuration --- shutdowncheck.sh | 81 ++++++++++++++++++++++++++++++++++-------------- shutdownirq.py | 30 +++++++++++++++--- 2 files changed, 82 insertions(+), 29 deletions(-) diff --git a/shutdowncheck.sh b/shutdowncheck.sh index 8e75c30..1d7a930 100755 --- a/shutdowncheck.sh +++ b/shutdowncheck.sh @@ -32,45 +32,78 @@ header() { echo "==========================================================================================" } -f2msec() { +f2nsec() { case "${1?}" in *.*.*) + echo 1>&2 "Not a valid decimal number: ${1}" return 1 ;; *.*) - __f2msec_whole="${1%%.*}" - __f2msec_fractional="${1#*.}" - - __f2msec_exp="${#__f2msec_fractional}" - __f2msec_scale=1 - while [ "$__f2msec_exp" -gt 0 ]; do - __f2msec_scale="$(( __f2msec_scale * 10 ))" - __f2msec_exp="$(( __f2msec_exp - 1 ))" + __f2nsec_whole="${1%%.*}" + __f2nsec_fractional="${1#*.}" + + __f2nsec_exp="${#__f2nsec_fractional}" + __f2nsec_scale=1 + + while [ "$__f2nsec_exp" -gt 0 ]; do + __f2nsec_scale="$(( __f2nsec_scale * 10 ))" + __f2nsec_exp="$(( __f2nsec_exp - 1 ))" done - echo "$(( (__f2msec_whole * 1000) + ((__f2msec_fractional * 1000) / __f2msec_scale) ))" + echo "$(( (__f2nsec_whole * 1000000000) + ((__f2nsec_fractional * 1000000000) / __f2nsec_scale) ))" ;; *) - echo "$(( "$1" * 1000 ))" + echo "$(( "$1" * 1000000000 ))" ;; esac } +ATX_RASPI_PULSE_MIN="${ATX_RASPI_PULSE_MIN:-0.2}" +ATX_RASPI_PULSE_MAX="${ATX_RASPI_PULSE_MAX:-0.6}" + +REBOOTPULSEMINIMUM="$(f2nsec "$ATX_RASPI_PULSE_MIN")" #reboot pulse signal should be at least this long +REBOOTPULSEMAXIMUM="$(f2nsec "$ATX_RASPI_PULSE_MAX")" #reboot pulse signal should be at most this long + #This is GPIO 7 (pin 26 on the pinout diagram). #This is an input from ATXRaspi to the Pi. -#When button is held for ~3 seconds, this pin will become HIGH signalling to this script to poweroff the Pi. -SHUTDOWN=7 -REBOOTPULSEMINIMUM=200 #reboot pulse signal should be at least this long -REBOOTPULSEMAXIMUM=600 #reboot pulse signal should be at most this long +#When button is held for ~3 seconds, this pin will become HIGH signaling to this script to poweroff the Pi. +SHUTDOWN="${ATX_RASPI_SHUTDOWN_PIN:-7}" #Added reboot feature (with ATXRaspi R2.6 (or ATXRaspi 2.5 with blue dot on chip) #Hold ATXRaspi button for at least 500ms but no more than 2000ms and a reboot HIGH pulse of 500ms length will be issued #This is GPIO 8 (pin 24 on the pinout diagram). #This is an output from Pi to ATXRaspi and signals that the Pi has booted. #This pin is asserted HIGH as soon as this script runs (by writing "1" to /sys/class/gpio/gpio8/value) -BOOT=8 +BOOT="${ATX_RASPI_BOOT_PIN:-8}" + +CHIP="${ATX_RASPI_CHIP:-/dev/gpiochip0}" + +failed='' + +if [ "$REBOOTPULSEMINIMUM" -le 0 ]; then + failed="${failed:+"${failed}, "}ATX_RASPI_PULSE_MIN (${ATX_RASPI_PULSE_MIN}) must be greater than 0" +fi + +if [ "$REBOOTPULSEMAXIMUM" -le "$REBOOTPULSEMINIMUM" ]; then + failed="${failed:+"${failed}, "}ATX_RASPI_PULSE_MAX (${ATX_RASPI_PULSE_MAX}) must be greater than ATX_RASPI_PULSE_MIN (${ATX_RASPI_PULSE_MIN})" +fi + +if [ "$SHUTDOWN" -lt 0 ]; then + failed="${failed:+"${failed}, "}ATX_RASPI_SHUTDOWN_PIN (${SHUTDOWN}) must be greater than 0" +fi + +if [ "$BOOT" -lt 0 ]; then + failed="${failed:+"${failed}, "}ATX_RASPI_BOOT_PIN (${BOOT}) must be greater than 0" +fi -CHIP="${CHIP:-/dev/gpiochip0}" +if [ "$SHUTDOWN" -eq "$BOOT" ]; then + failed="${failed:+"${failed}, "}ATX_RASPI_SHUTDOWN_PIN (${SHUTDOWN}) must be distinct from ATX_RASPI_BOOT_PIN (${BOOT})" +fi + +if [ -n "${failed:-}" ]; then + echo 1>&2 "ERROR: $failed" + exit 1 +fi if { command -v gpioset && command -v gpiomon ; } 1>/dev/null 2>&1; then init_shutdown_pin() { @@ -87,18 +120,18 @@ if { command -v gpioset && command -v gpiomon ; } 1>/dev/null 2>&1; then case "$event" in # Rising 1) - pulseStart="$(f2msec "$seconds")" + pulseStart="$(f2nsec "$seconds")" ;; # Falling 2) - pulseEnd="$(f2msec "$seconds")" + pulseEnd="$(f2nsec "$seconds")" pulseStart="${pulseStart:-"$pulseEnd"}" pulseDuration="$(( pulseEnd - pulseStart ))" - if [ "$pulseDuration" -gt $REBOOTPULSEMAXIMUM ]; then + if [ "$pulseDuration" -gt "$REBOOTPULSEMAXIMUM" ]; then header 12 "SHUTDOWN request on chip ${CHIP?} from GPIO${SHUTDOWN}, halting Rpi ..." return - elif [ "$pulseDuration" -gt $REBOOTPULSEMINIMUM ]; then + elif [ "$pulseDuration" -gt "$REBOOTPULSEMINIMUM" ]; then header 12 "REBOOT request on chip ${CHIP?} from GPIO${SHUTDOWN?}, recycling Rpi ..." reboot return @@ -133,10 +166,10 @@ elif [ -e /sys/class/gpio/export ]; then if [ "$shutdownSignal" = 0 ]; then sleep 0.2 else - pulseStart=$(date +%s%N | cut -b1-13) # mark the time when Shutoff signal went HIGH (milliseconds since epoch) + pulseStart="$(date +%s%N)" # mark the time when Shutoff signal went HIGH (milliseconds since epoch) while [ "$shutdownSignal" = 1 ]; do sleep 0.02 - if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then + if [ "$(( "$(date +%s%N)" - pulseStart ))" -gt "$REBOOTPULSEMAXIMUM" ]; then header 12 "SHUTDOWN request from GPIO${SHUTDOWN}, halting Rpi ..." poweroff return @@ -144,7 +177,7 @@ elif [ -e /sys/class/gpio/export ]; then shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) done #pulse went LOW, check if it was long enough, and trigger reboot - if [ $(($(date +%s%N | cut -b1-13)-pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then + if [ "$(( "$(date +%s%N)" - pulseStart ))" -gt "$REBOOTPULSEMINIMUM" ]; then header 12 "REBOOT request from GPIO${SHUTDOWN}, recycling Rpi ..." reboot return diff --git a/shutdownirq.py b/shutdownirq.py index 0e98683..8eef77b 100755 --- a/shutdownirq.py +++ b/shutdownirq.py @@ -8,16 +8,36 @@ import time # Reboot pulse signal should be at least this long (seconds). -REBOOTPULSEMINIMUM = 0.2 +REBOOTPULSEMINIMUM = float(os.environ.get("ATX_RASPI_PULSE_MIN", 0.2)) # Reboot pulse signal should be at most this long (seconds). -REBOOTPULSEMAXIMUM = 1.0 +REBOOTPULSEMAXIMUM = float(os.environ.get("ATX_RASPI_PULSE_MAX", 1.0)) # GPIO pin used for shutdown signal. -SHUTDOWN = 7 +SHUTDOWN = int(os.environ.get("ATX_RASPI_SHUTDOWN_PIN", 7)) # GPIO pin used for boot signal. -BOOT = 8 +BOOT = int(os.environ.get("ATX_RASPI_BOOT_PIN", 8)) + +failed = [msg for (cond, msg) in [ + (REBOOTPULSEMINIMUM > 0, "ATX_RASPI_PULSE_MIN ({0}) must be greater than 0".format(REBOOTPULSEMINIMUM)), + (REBOOTPULSEMAXIMUM > REBOOTPULSEMINIMUM, "ATX_RASPI_PULSE_MAX ({0} must be greater than ATX_RASPI_PULSE_MIN ({1})".format(REBOOTPULSEMAXIMUM, REBOOTPULSEMINIMUM)), + (SHUTDOWN >= 0, "ATX_RASPI_SHUTDOWN_PIN ({0}) must be greater than 0".format(SHUTDOWN)), + (BOOT >= 0, "ATX_RASPI_BOOT_PIN ({0}) must be greater than 0".format(BOOT)), + (SHUTDOWN != 0, "ATX_RASPI_SHUTDOWN_PIN ({0}) must be distinct from ATX_RASPI_BOOT_PIN ({1})".format(SHUTDOWN, BOOT)), +] if not cond] + +if len(failed) > 0: + raise ValueError(", ".join(failed)) + +if os.environ.get("ATX_RASPI_DRY_RUN", "") == "": + def handle_press(*command): + os.system(*command) + sys.exit() +else: + def handle_press(*command): + print("[dry-run] ", " ".join(command)) + sys.exit() def diag(*msgs): linelen = max([len(msg) for msg in msgs]) + 2 @@ -43,7 +63,7 @@ def announce(): if have_gpiod: - CHIP = "/dev/gpiochip0" + CHIP = os.environ.get("ATX_RASPI_CHIP", "/dev/gpiochip0") CONSUMER = "atx-raspi" CONFIG = { SHUTDOWN: gpiod.LineSettings(direction=Direction.INPUT, edge_detection=Edge.BOTH), From 2291d4acba2044c3a9d1be451f33371ae3765b59 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Tue, 25 Mar 2025 00:10:24 -0400 Subject: [PATCH 21/28] feat: support dry-run mode --- shutdowncheck.sh | 17 ++++++++++++++--- shutdownirq.py | 17 +++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/shutdowncheck.sh b/shutdowncheck.sh index 1d7a930..a61a1b7 100755 --- a/shutdowncheck.sh +++ b/shutdowncheck.sh @@ -105,6 +105,16 @@ if [ -n "${failed:-}" ]; then exit 1 fi +if [ -n "${ATX_RASPI_DRY_RUN:-}" ]; then + handle_press() { + echo "[dry-run] $*" + } +else + handle_press() { + "$@" + } +fi + if { command -v gpioset && command -v gpiomon ; } 1>/dev/null 2>&1; then init_shutdown_pin() { # NOP @@ -130,10 +140,11 @@ if { command -v gpioset && command -v gpiomon ; } 1>/dev/null 2>&1; then if [ "$pulseDuration" -gt "$REBOOTPULSEMAXIMUM" ]; then header 12 "SHUTDOWN request on chip ${CHIP?} from GPIO${SHUTDOWN}, halting Rpi ..." + handle_press poweroff return elif [ "$pulseDuration" -gt "$REBOOTPULSEMINIMUM" ]; then header 12 "REBOOT request on chip ${CHIP?} from GPIO${SHUTDOWN?}, recycling Rpi ..." - reboot + handle_press reboot return else unset pulseStart pulseEnd @@ -171,7 +182,7 @@ elif [ -e /sys/class/gpio/export ]; then sleep 0.02 if [ "$(( "$(date +%s%N)" - pulseStart ))" -gt "$REBOOTPULSEMAXIMUM" ]; then header 12 "SHUTDOWN request from GPIO${SHUTDOWN}, halting Rpi ..." - poweroff + handle_press poweroff return fi shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) @@ -179,7 +190,7 @@ elif [ -e /sys/class/gpio/export ]; then #pulse went LOW, check if it was long enough, and trigger reboot if [ "$(( "$(date +%s%N)" - pulseStart ))" -gt "$REBOOTPULSEMINIMUM" ]; then header 12 "REBOOT request from GPIO${SHUTDOWN}, recycling Rpi ..." - reboot + handle_press reboot return fi fi diff --git a/shutdownirq.py b/shutdownirq.py index 8eef77b..2b502fc 100755 --- a/shutdownirq.py +++ b/shutdownirq.py @@ -39,6 +39,15 @@ def handle_press(*command): print("[dry-run] ", " ".join(command)) sys.exit() +if os.environ.get("ATX_RASPI_DRY_RUN", "") == "": + def handle_press(*command): + os.system(*command) + sys.exit() +else: + def handle_press(*command): + print("[dry-run] ", " ".join(command)) + sys.exit() + def diag(*msgs): linelen = max([len(msg) for msg in msgs]) + 2 wrapper = "=" * linelen @@ -92,12 +101,12 @@ def announce(): if pulse_duration >= REBOOTPULSEMAXIMUM: print() diag("SHUTDOWN request on chip {0} from GPIO{1}, halting Rpi ...".format(CHIP, SHUTDOWN)) - os.system("poweroff") + handle_press("poweroff") sys.exit() elif pulse_duration >= REBOOTPULSEMINIMUM: print() diag("REBOOT request on chip {0} from GPIO{1}, recycling Rpi ...".format(CHIP, SHUTDOWN)) - os.system("reboot") + handle_press("reboot") sys.exit() else: pulse_start = None @@ -125,13 +134,13 @@ def announce(): if(time.time() - pulse_start >= REBOOTPULSEMAXIMUM): print() diag("SHUTDOWN request from GPIO{0}, halting Rpi ...".format(SHUTDOWN)) - os.system("poweroff") + handle_press("poweroff") sys.exit() shutdown_signal = GPIO.input(SHUTDOWN) if time.time() - pulse_start >= REBOOTPULSEMINIMUM: print() diag("REBOOT request from GPIO{0}, recycling Rpi ...".format(SHUTDOWN)) - os.system("reboot") + handle_press("reboot") sys.exit() if GPIO.input(SHUTDOWN): # before looping we must make sure the shutdown signal went low GPIO.wait_for_edge(SHUTDOWN, GPIO.FALLING) From c6fd4cdf051c481dd3a54cc5e58dbd44edd143f2 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Tue, 25 Mar 2025 00:10:43 -0400 Subject: [PATCH 22/28] chore: shell style --- shutdowncheck.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shutdowncheck.sh b/shutdowncheck.sh index a61a1b7..49a9304 100755 --- a/shutdowncheck.sh +++ b/shutdowncheck.sh @@ -173,7 +173,7 @@ elif [ -e /sys/class/gpio/export ]; then #ATXRaspi (GPIO7 to become HIGH), and issues a shutdown when that happens. #It sleeps as long as that has not happened. while true; do - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) + shutdownSignal="$(cat "/sys/class/gpio/gpio${SHUTDOWN}/value")" if [ "$shutdownSignal" = 0 ]; then sleep 0.2 else @@ -185,7 +185,7 @@ elif [ -e /sys/class/gpio/export ]; then handle_press poweroff return fi - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) + shutdownSignal="$(cat "/sys/class/gpio/gpio${SHUTDOWN}/value")" done #pulse went LOW, check if it was long enough, and trigger reboot if [ "$(( "$(date +%s%N)" - pulseStart ))" -gt "$REBOOTPULSEMINIMUM" ]; then From b159c0c3f214f9be1f22049f657ba78e75c72433 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Sat, 17 Feb 2024 10:45:53 -0500 Subject: [PATCH 23/28] feat: init Nix flake --- .gitignore | 13 ++ doc/nixos-modules.md | 206 +++++++++++++++++++++++++++ flake.lock | 103 ++++++++++++++ flake.nix | 70 +++++++++ nix/checks.nix | 116 +++++++++++++++ nix/devshells.nix | 50 +++++++ nix/nixos-modules.nix | 236 +++++++++++++++++++++++++++++++ nix/packages.nix | 81 +++++++++++ nix/pkgs/gpiosimtest/default.nix | 13 ++ nix/pkgs/gpiosimtest/go.mod | 39 +++++ nix/pkgs/gpiosimtest/go.sum | 95 +++++++++++++ nix/pkgs/gpiosimtest/main.go | 201 ++++++++++++++++++++++++++ 12 files changed, 1223 insertions(+) create mode 100644 .gitignore create mode 100644 doc/nixos-modules.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/checks.nix create mode 100644 nix/devshells.nix create mode 100644 nix/nixos-modules.nix create mode 100644 nix/packages.nix create mode 100644 nix/pkgs/gpiosimtest/default.nix create mode 100644 nix/pkgs/gpiosimtest/go.mod create mode 100644 nix/pkgs/gpiosimtest/go.sum create mode 100644 nix/pkgs/gpiosimtest/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6688664 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# VM disk images +*.qcow2 + +# Nix build results +result +result-* +repl-result-* + +# NixOS test driver REPL history +.nixos-test-history + +# Binary executable. +/nix/pkgs/gpiosimtest/gpiosimtest diff --git a/doc/nixos-modules.md b/doc/nixos-modules.md new file mode 100644 index 0000000..b494496 --- /dev/null +++ b/doc/nixos-modules.md @@ -0,0 +1,206 @@ +## services\.atx-raspi-shutdown\.enable + + + +Whether to enable the ATX-Raspi shutdown daemon\. + + + +*Type:* +boolean + + + +*Default:* +` false ` + + + +*Example:* +` true ` + +*Declared by:* + - [flake\.nix](/flake.nix) + + + +## services\.atx-raspi-shutdown\.package + + + +Package providing the ATX-Raspi daemon\. + + + +*Type:* +package + + + +*Default:* +` ` + +*Declared by:* + - [flake\.nix](/flake.nix) + + + +## services\.atx-raspi-shutdown\.chip + +The path to the GPIO chip\. + + + +*Type:* +null or absolute path + + + +*Default:* +` null ` + + + +*Example:* +` "/dev/gpiochip42" ` + +*Declared by:* + - [flake\.nix](/flake.nix) + + + +## services\.atx-raspi-shutdown\.implementation + + + +GPIO daemon implementation\. “irq” selects the +edge-triggered Python implementation\. “check” selects +the polling-based shell implementation\. + + + +*Type:* +one of “check”, “irq” + + + +*Default:* +` "irq" ` + + + +*Example:* +` "check" ` + +*Declared by:* + - [flake\.nix](/flake.nix) + + + +## services\.atx-raspi-shutdown\.pins\.boot + + + +Pin on ` chip ` to set high upon ATX-Raspi +service startup\. + + + +*Type:* +null or (unsigned integer, meaning >=0) + + + +*Default:* +` null ` + + + +*Example:* +` 8 ` + +*Declared by:* + - [flake\.nix](/flake.nix) + + + +## services\.atx-raspi-shutdown\.pins\.shutdown + + + +Pin on ` chip ` to watch for the shutdown or +reboot button press\. + + + +*Type:* +null or (unsigned integer, meaning >=0) + + + +*Default:* +` null ` + + + +*Example:* +` 7 ` + +*Declared by:* + - [flake\.nix](/flake.nix) + + + +## services\.atx-raspi-shutdown\.pulses\.reboot + + + +Minimum duration of high state on +` pins.shutdown ` to trigger reboot\. + + + +*Type:* +null or floating point number + + + +*Default:* +` null ` + + + +*Example:* +` 0.2 ` + +*Declared by:* + - [flake\.nix](/flake.nix) + + + +## services\.atx-raspi-shutdown\.pulses\.shutdown + + + +Minimum duration of high state on +` pins.shutdown ` to trigger shutdown\. + + + +*Type:* +null or floating point number + + + +*Default:* +` null ` + + + +*Example:* +` 0.6 ` + +*Declared by:* + - [flake\.nix](/flake.nix) + + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c24a65b --- /dev/null +++ b/flake.lock @@ -0,0 +1,103 @@ +{ + "nodes": { + "devshell": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741473158, + "narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=", + "owner": "numtide", + "repo": "devshell", + "rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1741352980, + "narHash": "sha256-+u2UunDA4Cl5Fci3m7S643HzKmIDAe+fiXrLqYsR2fs=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f4330d22f1c5d2ba72d3d22df5597d123fdb60a9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1742669843, + "narHash": "sha256-G5n+FOXLXcRx+3hCJ6Rt6ZQyF1zqQ0DL0sWAMn2Nk0w=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1e5b653dff12029333a6546c11e108ede13052eb", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1740877520, + "narHash": "sha256-oiwv/ZK/2FhGxrCkQkB83i7GnWXPPLzoqFHpDD3uYpk=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "147dee35aab2193b174e4c0868bd80ead5ce755c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "root": { + "inputs": { + "devshell": "devshell", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1742370146, + "narHash": "sha256-XRE8hL4vKIQyVMDXykFh4ceo3KSpuJF3ts8GKwh5bIU=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "adc195eef5da3606891cedf80c0d9ce2d3190808", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..9dddcc7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,70 @@ +{ + description = "Description for the project"; + + inputs = { + devshell.url = "github:numtide/devshell"; + devshell.inputs.nixpkgs.follows = "nixpkgs"; + + flake-parts.url = "github:hercules-ci/flake-parts"; + + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + treefmt-nix.url = "github:numtide/treefmt-nix"; + treefmt-nix.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = inputs: + inputs.flake-parts.lib.mkFlake {inherit inputs;} ({ + inputs, + moduleWithSystem, + self, + ... + }: { + systems = [ + "aarch64-darwin" + "aarch64-linux" + "x86_64-darwin" + "x86_64-linux" + ]; + + imports = [ + inputs.devshell.flakeModule + inputs.treefmt-nix.flakeModule + + ./nix/checks.nix + ./nix/devshells.nix + ./nix/nixos-modules.nix + ./nix/packages.nix + ]; + + flake = { + nixosConfigurations = { + default = inputs.nixpkgs.lib.nixosSystem { + modules = [ + self.nixosModules.atx-raspi + { + boot.isContainer = true; + fileSystems."/".fsType = "tmpfs"; + nixpkgs.hostPlatform = "x86_64-linux"; + system.stateVersion = "24.11"; + } + ]; + }; + }; + }; + + perSystem = { + config, + lib, + ... + }: { + apps = lib.mapAttrs (lib.const (lib.getAttr "flakeApp")) config.devShells; + + treefmt = {config, ...}: { + flakeFormatter = true; + projectRootFile = "flake.nix"; + programs.alejandra.enable = true; + }; + }; + }); +} diff --git a/nix/checks.nix b/nix/checks.nix new file mode 100644 index 0000000..c9aa181 --- /dev/null +++ b/nix/checks.nix @@ -0,0 +1,116 @@ +{self, ...}: { + perSystem = { + config, + lib, + pkgs, + ... + }: { + checks = { + default = config.checks.atx-raspi; + + atx-raspi = pkgs.nixosTest { + name = "atx-raspi-shutdown"; + nodes = let + common = {pkgs, ...}: { + imports = [self.nixosModules.default]; + + boot.kernelModules = ["gpio-sim"]; + + environment.systemPackages = with pkgs; [ + curl + jq + ]; + + security.polkit.debug = true; + security.polkit.enable = true; + + services.atx-raspi-shutdown.enable = true; + + systemd.services.gpiosimtest = { + wantedBy = ["multi-user.target"]; + environment.PORT = "8080"; + serviceConfig = { + ExecStart = lib.getExe config.packages.gpiosimtest; + }; + }; + + systemd.services.atx-raspi-shutdown = { + wantedBy = lib.mkForce []; # Need to start manually. + requires = ["gpiosimtest.service"]; + after = ["gpiosimtest.service"]; + environment.ATX_RASPI_DRY_RUN = "1"; + }; + }; + in { + check = {...}: { + imports = [common]; + services.atx-raspi-shutdown.implementation = "check"; + }; + + irq = {...}: { + imports = [common]; + services.atx-raspi-shutdown.implementation = "irq"; + }; + }; + testScript = {nodes, ...}: let + checkURL = "http://localhost:${nodes.check.systemd.services.gpiosimtest.environment.PORT}"; + irqURL = "http://localhost:${nodes.irq.systemd.services.gpiosimtest.environment.PORT}"; + in '' + import time + + from typing import Optional + + def systemctl_succeed(m: Machine, q: str, user: Optional[str] = None) -> str: + user_desc = "root" + if user is not None: + user_desc = user + with m.nested(f"command `systemctl {q}` must succeed as {user_desc}"): + (status, output) = m.systemctl(q, user) + + if status != 0: + m.log(f"output: {output}") + raise Exception(f"command `systemctl {q}` failed as {user_desc} (exit code {status})") + + return output + + start_all() + + check.succeed("modprobe gpio_sim 1>&2") + check.succeed("lsmod | grep gpio_sim 1>&2") + + check.wait_for_unit("dbus.service") + + systemctl_succeed(check, "cat polkit.service 1>&2") + systemctl_succeed(check, "cat atx-raspi-shutdown.service 1>&2") + + systemctl_succeed(irq, "cat polkit.service 1>&2") + systemctl_succeed(irq, "cat atx-raspi-shutdown.service 1>&2") + + check.wait_for_unit("gpiosimtest.service") + check_chip = check.succeed("curl ${checkURL}/devpath | jq --raw-output '.data.devpath'").strip() + systemctl_succeed(check, f"set-environment ATX_RASPI_CHIP={check_chip}") + + irq.wait_for_unit("gpiosimtest.service") + irq_chip = irq.succeed("curl ${irqURL}/devpath | jq --raw-output '.data.devpath'").strip() + systemctl_succeed(irq, f"set-environment ATX_RASPI_CHIP={irq_chip}") + + systemctl_succeed(check, "start atx-raspi-shutdown.service") + check.wait_for_unit("atx-raspi-shutdown.service") + check.wait_until_succeeds("test $(curl ${checkURL}/line/8/level | jq --raw-output '.data.level') = 1") + check.wait_until_succeeds("curl -X PUT ${checkURL}/line/7/pull/up | jq 1>&2") + time.sleep(5) + check.wait_until_succeeds("curl -X PUT ${checkURL}/line/7/pull/down | jq 1>&2") + check.wait_until_succeeds(f"journalctl --grep='SHUTDOWN request on chip {check_chip}'") + + systemctl_succeed(irq, "start atx-raspi-shutdown.service") + irq.wait_for_unit("atx-raspi-shutdown.service") + irq.wait_until_succeeds("test $(curl ${irqURL}/line/8/level | jq --raw-output '.data.level') = 1") + irq.wait_until_succeeds("curl -X PUT ${irqURL}/line/7/pull/up | jq 1>&2") + time.sleep(5) + irq.wait_until_succeeds("curl -X PUT ${irqURL}/line/7/pull/down | jq 1>&2") + check.wait_until_succeeds(f"journalctl --grep='SHUTDOWN request on chip {irq_chip}'") + ''; + }; + }; + }; +} diff --git a/nix/devshells.nix b/nix/devshells.nix new file mode 100644 index 0000000..aa3099f --- /dev/null +++ b/nix/devshells.nix @@ -0,0 +1,50 @@ +{ + perSystem = { + config, + lib, + pkgs, + ... + }: { + devShells = { + default = config.devShells.atx-raspi; + }; + + devshells = { + atx-raspi = {extraModulesPath, ...}: { + imports = ["${extraModulesPath}/language/go.nix"]; + + commands = + [ + { + package = pkgs.act; + } + + { + package = pkgs.libgpiod; + } + + { + package = config.treefmt.build.wrapper; + } + + { + name = "mkoptdocs"; + command = '' + cd "$(git rev-parse --show-cdup)" || exit + while read -r out_path; do + install -Dm0644 "$out_path" ./doc/nixos-modules.md + done < <(nix build "$@" --no-link --print-out-paths '.#docs') + ''; + help = "Build NixOS module options documentation"; + } + ] + ++ lib.pipe config.packages [ + (lib.flip builtins.removeAttrs ["default"]) + (builtins.attrValues) + (lib.filter (package: package ? meta.mainProgram)) + (map (package: {inherit package;})) + ]; + }; + }; + }; +} diff --git a/nix/nixos-modules.nix b/nix/nixos-modules.nix new file mode 100644 index 0000000..89453be --- /dev/null +++ b/nix/nixos-modules.nix @@ -0,0 +1,236 @@ +{ + self, + moduleWithSystem, + ... +}: { + flake = { + nixosModules = { + default = self.nixosModules.atx-raspi; + + atx-raspi = moduleWithSystem ( + {config, ...} @ perSystem: { + config, + lib, + options, + ... + }: let + inherit (lib) mkOption types; + cfg = config.services.atx-raspi-shutdown; + opts = options.services.atx-raspi-shutdown; + description = "ATX-Raspi shutdown daemon"; + polkitEnabled = config.security.polkit.enable; + wrap = lib.replaceStrings ["\n"] [" "]; + in { + options = { + services = { + atx-raspi-shutdown = { + enable = lib.mkEnableOption "the ${description}"; + + implementation = mkOption { + type = types.enum ["check" "irq"]; + default = "irq"; + example = "check"; + description = '' + GPIO daemon implementation. "irq" selects the + edge-triggered Python implementation. "check" selects + the polling-based shell implementation. + ''; + }; + + package = mkOption { + type = types.package; + default = perSystem.config.packages."atx-raspi-shutdown${cfg.implementation}"; + description = '' + Package providing the ATX-Raspi daemon. + ''; + }; + + chip = mkOption { + type = types.nullOr types.path; + default = null; + example = "/dev/gpiochip42"; + description = '' + The path to the GPIO chip. + ''; + }; + + pins = { + shutdown = mkOption { + type = types.nullOr types.ints.unsigned; + default = null; + example = 7; + description = '' + Pin on {option}`chip` to watch for the shutdown or + reboot button press. + ''; + }; + + boot = mkOption { + inherit (opts.pins.shutdown) type; + default = null; + example = 8; + description = '' + Pin on {option}`chip` to set high upon ATX-Raspi + service startup. + ''; + }; + }; + + pulses = { + reboot = mkOption { + type = types.nullOr types.float; + default = null; + example = 0.2; + description = '' + Minimum duration of high state on + {option}`pins.shutdown` to trigger reboot. + ''; + }; + + shutdown = mkOption { + inherit (opts.pulses.reboot) type; + default = null; + example = 0.6; + description = '' + Minimum duration of high state on + {option}`pins.shutdown` to trigger shutdown. + ''; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.atx-raspi-shutdown = lib.const { + imports = [ + (lib.mkIf polkitEnabled { + #serviceConfig.DynamicUser = true; + }) + ]; + + description = "The ${description}"; + + wantedBy = ["basic.target"]; + + environment = + lib.flip lib.pipe [ + (lib.filterAttrs (_: value: value != null)) + (lib.mapAttrs' (name: value: { + inherit value; + name = "ATX_RASPI_${lib.toUpper name}"; + })) + ] { + inherit (cfg) chip; + boot_pin = cfg.pins.boot; + shutdown_pin = cfg.pins.shutdown; + pulse_min = cfg.pulses.reboot; + pulse_max = cfg.pulses.shutdown; + }; + + serviceConfig = { + UMask = "0027"; + + Restart = "always"; + RestartSec = 3; + + BindPaths = + [ + #"/run/dbus/system_bus_socket" + ] + ++ lib.optional (cfg.chip != null) cfg.chip; + + ExecStart = lib.getExe cfg.package; + + # Security settings + CapabilityBoundingSet = [""]; + DevicePolicy = "closed"; + DeviceAllow = "char-gpiochip"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = false; + PrivateIPC = true; + PrivateTmp = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service @resources" + "~@privileged" + ]; + }; + }; + + assertions = let + noNullAttrValues = attrs: lib.all (x: x != null) (builtins.attrValues attrs); + in [ + { + assertion = (noNullAttrValues cfg.pins) -> (cfg.pins.boot != cfg.pins.shutdown); + message = wrap '' + atx-raspi: boot pin and shutdown pin must be distinct + ''; + } + + { + assertion = (noNullAttrValues cfg.pulses) -> (cfg.pulses.reboot > cfg.pulses.shutdown); + message = wrap '' + atx-raspi: shutdown pulse must be longer than reboot pulse + (reboot pulse is ${toString cfg.pulses.reboot}, shutdown + pulse is ${toString cfg.pulses.poweroff}) + ''; + } + ]; + + warnings = lib.optional (!polkitEnabled) (wrap '' + atx-raspi: `security.polkit.enable` is set to `false`; running + the GPIO daemon as `root`. + ''); + + security.polkit.extraConfig = '' + // Permit ATX-Raspi service users to power off and reboot + polkit.addRule(function(action, subject) { + if ( + ( + action.id == "org.freedesktop.login1.power-off" + || action.id == "org.freedesktop.login1.power-off-multiple-sessions" + || action.id == "org.freedesktop.login1.power-off-ignore-inhibit" + || action.id == "org.freedesktop.login1.reboot" + || action.id == "org.freedesktop.login1.reboot-multiple-sessions" + || action.id == "org.freedesktop.login1.reboot-ignore-inhibit" + || action.id == "org.freedesktop.login1.suspend" + || action.id == "org.freedesktop.login1.suspend-multiple-sessions" + || action.id == "org.freedesktop.login1.suspend-ignore-inhibit" + || action.id == "org.freedesktop.login1.hibernate" + || action.id == "org.freedesktop.login1.hibernate-multiple-sessions" + || action.id == "org.freedesktop.login1.hibernate-ignore-inhibit" + ) && subject.user == "${config.systemd.services.atx-raspi-shutdown.serviceConfig.User or "atx-raspi-shutdown"}" + ) { + return polkit.Result.YES; + } + }); + ''; + }; + } + ); + }; + }; +} diff --git a/nix/packages.nix b/nix/packages.nix new file mode 100644 index 0000000..9fcfed3 --- /dev/null +++ b/nix/packages.nix @@ -0,0 +1,81 @@ +{self, ...}: { + perSystem = { + config, + pkgs, + system, + ... + }: { + packages = { + default = config.packages.atx-raspi-shutdownirq; + + atx-raspi-shutdownirq = pkgs.callPackage ({ + writers, + python3, + }: + writers.makeScriptWriter { + inherit (python3.withPackages (p: [p.libgpiod])) interpreter; + } "/bin/atx-raspi-shutdownirq" (builtins.readFile "${self}/shutdownirq.py")) {}; + + atx-raspi-shutdowncheck = pkgs.callPackage ({ + coreutils, + libgpiod, + lib, + writers, + }: + writers.writeBashBin "atx-raspi-shutdowncheck" { + makeWrapperArgs = [ + "--prefix" + "PATH" + ":" + (lib.makeBinPath [coreutils libgpiod]) + ]; + } (builtins.readFile "${self}/shutdowncheck.sh")) {}; + + docs = pkgs.callPackage ({ + nixosOptionsDoc, + lib, + eval, + }: + (nixosOptionsDoc { + options = { + inherit (eval.options.services) atx-raspi-shutdown; + }; + + # Default is currently "appendix". + documentType = "none"; + + warningsAreErrors = true; + + transformOptions = let + ourPrefix = "${toString self}/"; + moduleSource = "flake.nix"; + link = { + url = "/${moduleSource}"; + name = moduleSource; + }; + in + opt: + opt + // { + visible = opt.visible && (lib.any (lib.hasPrefix ourPrefix) opt.declarations); + declarations = map (decl: + if lib.hasPrefix ourPrefix decl + then link + else decl) + opt.declarations; + }; + }) + .optionsCommonMark) { + eval = self.nixosConfigurations.default.extendModules { + modules = [ + { + nixpkgs.hostPlatform = system; + } + ]; + }; + }; + + gpiosimtest = pkgs.callPackage ./pkgs/gpiosimtest {}; + }; + }; +} diff --git a/nix/pkgs/gpiosimtest/default.nix b/nix/pkgs/gpiosimtest/default.nix new file mode 100644 index 0000000..be82aa8 --- /dev/null +++ b/nix/pkgs/gpiosimtest/default.nix @@ -0,0 +1,13 @@ +{buildGoModule}: +buildGoModule { + pname = "gpiosimtest"; + version = "0.1.0"; + src = ./.; + vendorHash = "sha256-wufUG34GPZLdcuhVls06o3uiVf0wYUVjFQOdbYbZ5pg="; + tags = ["nomsgpack"]; + meta = { + description = "GPIO chip simulation API"; + homepage = "https://github.com/LowPowerLab/ATX-Raspi"; + mainProgram = "gpiosimtest"; + }; +} diff --git a/nix/pkgs/gpiosimtest/go.mod b/nix/pkgs/gpiosimtest/go.mod new file mode 100644 index 0000000..b72aa88 --- /dev/null +++ b/nix/pkgs/gpiosimtest/go.mod @@ -0,0 +1,39 @@ +module github.com/LowPowerLab/ATX-Raspi/gpiosimtest + +go 1.21.4 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/warthog618/go-gpiosim v0.1.1 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/warthog618/go-gpiocdev v0.9.1 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/nix/pkgs/gpiosimtest/go.sum b/nix/pkgs/gpiosimtest/go.sum new file mode 100644 index 0000000..70a0f2c --- /dev/null +++ b/nix/pkgs/gpiosimtest/go.sum @@ -0,0 +1,95 @@ +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/warthog618/go-gpiocdev v0.9.1 h1:pwHPaqjJfhCipIQl78V+O3l9OKHivdRDdmgXYbmhuCI= +github.com/warthog618/go-gpiocdev v0.9.1/go.mod h1:dN3e3t/S2aSNC+hgigGE/dBW8jE1ONk9bDSEYfoPyl8= +github.com/warthog618/go-gpiosim v0.1.1 h1:MRAEv+T+itmw+3GeIGpQJBfanUVyg0l3JCTwHtwdre4= +github.com/warthog618/go-gpiosim v0.1.1/go.mod h1:YXsnB+I9jdCMY4YAlMSRrlts25ltjmuIsrnoUrBLdqU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/nix/pkgs/gpiosimtest/main.go b/nix/pkgs/gpiosimtest/main.go new file mode 100644 index 0000000..4dc2270 --- /dev/null +++ b/nix/pkgs/gpiosimtest/main.go @@ -0,0 +1,201 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "strconv" + + "github.com/gin-gonic/gin" + gpiosim "github.com/warthog618/go-gpiosim" +) + +const ( + ENOLINE = -1 +) + +func retrieveLineOffset(g *gpiosim.Simpleton, spec string) (int, error) { + offset, err := strconv.Atoi(spec) + if err != nil { + return ENOLINE, err + } + + if offset < 0 || offset >= g.Config().NumLines { + return ENOLINE, fmt.Errorf("offset must be an integer between 0 and %d; got %d", g.Config().NumLines-1, offset) + } + + return offset, nil +} + +func handleLineOffset(c *gin.Context, g *gpiosim.Simpleton, spec string) (int, error) { + offset, err := retrieveLineOffset(g, spec) + if err != nil { + respondNotFound(c, err, gin.H{ + "offset": offset, + }) + } + + return offset, err +} + +func handleLevel(c *gin.Context, g *gpiosim.Simpleton, offset int) { + level, err := g.Level(offset) + if err != nil { + respondInternalServerError(c, err, gin.H{"offset": offset, "level": level}) + } else { + respondOk(c, gin.H{"offset": offset, "level": level}) + } +} + +func handlePull(c *gin.Context, g *gpiosim.Simpleton, offset int) { + pull, err := g.Pull(offset) + if err != nil { + respondInternalServerError(c, err, gin.H{"offset": offset, "pull": pull}) + } else { + respondOk(c, gin.H{"offset": offset, "pull": pull}) + } +} + +func respond(c *gin.Context, st int, err error, data gin.H) { + c.JSON(st, gin.H{"error": err, "data": data}) +} + +func respondOk(c *gin.Context, data gin.H) { + respond(c, http.StatusOK, nil, data) +} + +func respondBadRequest(c *gin.Context, err error, data gin.H) { + respond(c, http.StatusBadRequest, err, data) +} + +func respondNotFound(c *gin.Context, err error, data gin.H) { + respond(c, http.StatusNotFound, err, data) +} + +func respondInternalServerError(c *gin.Context, err error, data gin.H) { + respond(c, http.StatusInternalServerError, err, data) +} + +func main() { + lines, ok := os.LookupEnv("LINES") + var linei int + if ok { + linec, err := strconv.Atoi(lines) + if err != nil { + fmt.Printf("error parsing LINES specification %v: %v\n", lines, err) + os.Exit(1) + } else { + linei = linec + } + } else { + linei = 9 + } + + g, err := gpiosim.NewSimpleton(linei) + if err != nil { + fmt.Printf("error creating simulated GPIO: %v\n", err) + os.Exit(1) + } + + defer g.Close() + + r := gin.Default() + + r.GET("/", func(c *gin.Context) { + respondOk(c, gin.H{ + "chipname": g.ChipName(), + "config": g.Config(), + "devpath": g.DevPath(), + }) + }) + + r.GET("/chipname", func(c *gin.Context) { + respondOk(c, gin.H{"chipname": g.ChipName()}) + }) + + r.GET("/config", func(c *gin.Context) { + respondOk(c, gin.H{"config": g.Config()}) + }) + + r.GET("/devpath", func(c *gin.Context) { + respondOk(c, gin.H{"devpath": g.DevPath()}) + }) + + r.GET("/line/:offset/level", func(c *gin.Context) { + offset, err := handleLineOffset(c, g, c.Param("offset")) + if err != nil { + return + } + + handleLevel(c, g, offset) + }) + + r.GET("/line/:offset/pull", func(c *gin.Context) { + offset, err := handleLineOffset(c, g, c.Param("offset")) + if err != nil { + return + } + + handlePull(c, g, offset) + }) + + r.PUT("/line/:offset/pull/down", func(c *gin.Context) { + offset, err := handleLineOffset(c, g, c.Param("offset")) + if err != nil { + return + } + + if err := g.Pulldown(offset); err != nil { + respondInternalServerError(c, err, gin.H{"offset": offset}) + } else { + handlePull(c, g, offset) + } + }) + + r.PUT("/line/:offset/pull/up", func(c *gin.Context) { + offset, err := handleLineOffset(c, g, c.Param("offset")) + if err != nil { + return + } + + if err := g.Pullup(offset); err != nil { + respondInternalServerError(c, err, gin.H{"offset": offset}) + } else { + handlePull(c, g, offset) + } + }) + + r.PUT("/line/:offset/pull/:bias", func(c *gin.Context) { + offset, err := handleLineOffset(c, g, c.Param("offset")) + if err != nil { + return + } + + bias, err := strconv.Atoi(c.Param("bias")) + if err != nil { + respondBadRequest(c, err, gin.H{"offset": offset, "bias": bias}) + return + } + + if err := g.SetPull(offset, bias); err != nil { + respondInternalServerError(c, err, gin.H{"offset": offset, "bias": bias}) + } else { + handlePull(c, g, offset) + } + }) + + r.PUT("/line/:offset/pull/toggle", func(c *gin.Context) { + offset, err := handleLineOffset(c, g, c.Param("offset")) + if err != nil { + return + } + + if err := g.Toggle(offset); err != nil { + respondInternalServerError(c, err, gin.H{"offset": offset}) + } else { + handlePull(c, g, offset) + } + }) + + r.Run() +} From b2666255cc3f7377e20dc15db44a85abc818395a Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Tue, 25 Mar 2025 08:21:51 -0400 Subject: [PATCH 24/28] feat: enable additional linters/formatters --- .editorconfig | 26 ++++++++++++++++++++++++++ flake.nix | 15 ++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..82ebf5c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +indent_style = space +indent_size = 2 + +[*.md] +max_line_length = off +trim_trailing_whitespace = false +indent_size = unset + +[*.py] +indent_size = 4 + +[*.sh] +indent_style = space +indent_size = 4 +binary_next_line = true +switch_case_indent = true +space_redirects = false +keep_padding = true diff --git a/flake.nix b/flake.nix index 9dddcc7..1e5ee45 100644 --- a/flake.nix +++ b/flake.nix @@ -63,7 +63,20 @@ treefmt = {config, ...}: { flakeFormatter = true; projectRootFile = "flake.nix"; - programs.alejandra.enable = true; + + programs = { + alejandra.enable = true; + gofumpt.enable = true; + ruff.enable = true; + shellcheck.enable = true; + shfmt.enable = true; + }; + + settings.formatter = lib.mkIf config.programs.shfmt.enable { + # Empty out the CLI options list so that `shfmt` uses the settings + # from `.editorconfig`. + shfmt.options = lib.mkForce []; + }; }; }; }); From 5af2abbb95ad58720c9799a1a8d2e23453d3687d Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Tue, 25 Mar 2025 08:22:12 -0400 Subject: [PATCH 25/28] fix(shutdownirq): no bare `except` --- shutdownirq.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shutdownirq.py b/shutdownirq.py index 2b502fc..db863b6 100755 --- a/shutdownirq.py +++ b/shutdownirq.py @@ -111,7 +111,7 @@ def announce(): else: pulse_start = None pulse_end = None - except: + except Exception: pass else: import RPi.GPIO as GPIO @@ -144,7 +144,7 @@ def announce(): sys.exit() if GPIO.input(SHUTDOWN): # before looping we must make sure the shutdown signal went low GPIO.wait_for_edge(SHUTDOWN, GPIO.FALLING) - except: + except Exception: pass finally: GPIO.cleanup() From 88a16980552475c5dc00fe4fe3830ff30821c354 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Tue, 25 Mar 2025 08:22:44 -0400 Subject: [PATCH 26/28] chore: apply formatters --- shutdownchecksetup.sh | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/shutdownchecksetup.sh b/shutdownchecksetup.sh index 617d18a..d5d9994 100644 --- a/shutdownchecksetup.sh +++ b/shutdownchecksetup.sh @@ -10,7 +10,7 @@ SHUTDOWNCHECK_BASEURL="${SHUTDOWNCHECK_BASEURL:-"https://raw.githubusercontent.c # We provide a fallback in case `EUID` is not set; silence shellcheck violation # warning that "In POSIX sh, EUID is undefined" # shellcheck disable=SC3028 -if [ "${EUID:-$(id -u 2>/dev/null || :)}" != 0 ]; then +if [ "${EUID:-$(id -u 2> /dev/null || :)}" != 0 ]; then run_as_root() { sudo "$@" } @@ -20,11 +20,11 @@ else } fi -if command -v curl 1>/dev/null 2>&1; then +if command -v curl 1> /dev/null 2>&1; then fetch() { run_as_root curl -fsSLo "$2" "$1" } -elif command -v wget 1>/dev/null 2>&1; then +elif command -v wget 1> /dev/null 2>&1; then fetch() { run_as_root wget -o "$2" "$1" } @@ -55,7 +55,7 @@ install_polling_script() { looks_like_elec_distro() { # shellcheck disable=SC1091 - . /etc/os-release 1>/dev/null 2>&1 || : + . /etc/os-release 1> /dev/null 2>&1 || : case "${NAME:-}" in *ELEC) @@ -75,7 +75,7 @@ looks_like_elec_distro() { # 2. Have the directory `/storage/.config`, and # 3. Use a `sudo` wrapper that issues a warning about not needing sudo # and then exits with a non-zero status. - [ "${EUID:-$(id -u 2>/dev/null || :)}" -eq 0 ] && [ -d /storage/.config ] && ! sudo true + [ "${EUID:-$(id -u 2> /dev/null || :)}" -eq 0 ] && [ -d /storage/.config ] && ! sudo true } if looks_like_elec_distro; then @@ -92,14 +92,14 @@ ${dest} exit fi -if command -v whiptail 1>/dev/null 2>&1; then +if command -v whiptail 1> /dev/null 2>&1; then get_script_type() { whiptail --title "ATXRaspi/MightyHat shutdown/reboot script setup" --menu "\nChoose your script type option below:\n\n(Note: changes require reboot to take effect)" 15 78 4 \ "1" "Install INTERRUPT based script /etc/shutdownirq.py (recommended)" \ "2" "Install POLLING based script /etc/shutdowncheck.sh (classic)" \ "3" "Disable any existing shutdown script" 3>&1 1>&2 2>&3 } -elif (help select) 1>/dev/null 2>&1; then +elif (help select) 1> /dev/null 2>&1; then # Eval this, as otherwise we're liable to get a syntax error from shells that # do not understand `select ...; do ...; done` eval ' @@ -132,13 +132,12 @@ else 2) Install POLLING based script /etc/shutdowncheck.sh (classic) 3) Disable any existing shutdown script" - while true; do echo 1>&2 'Choose your script type option: ' if read -r REPLY; then case "${REPLY:-}" in - 1|2|3) + 1 | 2 | 3) echo "$REPLY" return ;; From f3f60bbe56abb9a29aba6f7c1c9f0d72a174c6d6 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Tue, 25 Mar 2025 08:23:19 -0400 Subject: [PATCH 27/28] feat(ci): add GitHub actions config --- .github/workflows/test.yml | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..6834fef --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,41 @@ +name: Run ATX-Raspi tests +on: + pull_request: + push: + workflow_dispatch: +jobs: + flake: + name: Run Nix flake checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Initialize Nix store paths + run: | + sudo mkdir -p /nix/store + sudo chmod -R 777 /nix + # https://github.com/cachix/install-nix-action/issues/56#issuecomment-1030697681 + - name: Configure Nix store cache + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-${{ runner.arch }}-nix-store + # See https://github.com/actions/cache/pull/726 and + # https://github.com/actions/cache/issues/494 for caveats on negative + # patterns. + path: | + /nix/store/** + /nix/var/nix/*/* + /nix/var/nix/db/* + /nix/var/nix/db/*/** + !/nix/var/nix/daemon-socket/socket + !/nix/var/nix/userpool/* + !/nix/var/nix/gc.lock + !/nix/var/nix/db/big-lock + !/nix/var/nix/db/reserved + - name: Install Nix + uses: cachix/install-nix-action@v31 + with: + extra_nix_config: | + system-features = benchmark big-parallel kvm nixos-test uid-range + - name: Run Nix flake checks + run: | + nix flake check -L From c9caf2c48fb25d2eb72623b4886b719926556ab7 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Wed, 26 Mar 2025 10:17:38 -0400 Subject: [PATCH 28/28] feat: add .actrc that uses `runner-*` image so that we can run actions as a non-root user (needed for flake checks). --- .actrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .actrc diff --git a/.actrc b/.actrc new file mode 100644 index 0000000..d270424 --- /dev/null +++ b/.actrc @@ -0,0 +1 @@ +-P ubuntu-latest=catthehacker/ubuntu:runner-latest