You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

313 lines
8.9 KiB

  1. #!/bin/bash
  2. # CREDIT TO THESE TUTORIALS:
  3. # petr.io/en/blog/2015/11/09/read-only-raspberry-pi-with-jessie
  4. # hallard.me/raspberry-pi-read-only
  5. # k3a.me/how-to-make-raspberrypi-truly-read-only-reliable-and-trouble-free
  6. if [ $(id -u) -ne 0 ]; then
  7. echo "Installer must be run as root."
  8. echo "Try 'sudo bash $0'"
  9. exit 1
  10. fi
  11. clear
  12. echo "This script configures a Raspberry Pi"
  13. echo "SD card to boot into read-only mode,"
  14. echo "obviating need for clean shutdown."
  15. echo "NO FILES ON THE CARD CAN BE CHANGED"
  16. echo "WHEN PI IS BOOTED IN THIS STATE. Either"
  17. echo "the filesystems must be remounted in"
  18. echo "read/write mode, card must be mounted"
  19. echo "R/W on another system, or an optional"
  20. echo "jumper can be used to enable read/write"
  21. echo "on boot."
  22. echo
  23. echo "Links to original tutorials are in"
  24. echo "script source. THIS IS A ONE-WAY"
  25. echo "OPERATION. THERE IS NO SCRIPT TO"
  26. echo "REVERSE THIS SETUP! ALL other system"
  27. echo "config should be complete before using"
  28. echo "this script. MAKE A BACKUP FIRST."
  29. echo
  30. echo "Run time ~5 minutes. Reboot required."
  31. echo
  32. echo -n "CONTINUE? [y/N] "
  33. read
  34. if [[ ! "$REPLY" =~ ^(yes|y|Y)$ ]]; then
  35. echo "Canceled."
  36. exit 0
  37. fi
  38. # FEATURE PROMPTS ----------------------------------------------------------
  39. # Installation doesn't begin until after all user input is taken.
  40. INSTALL_RW_JUMPER=0
  41. INSTALL_HALT=0
  42. INSTALL_WATCHDOG=0
  43. # Given a list of strings representing options, display each option
  44. # preceded by a number (1 to N), display a prompt, check input until
  45. # a valid number within the selection range is entered.
  46. selectN() {
  47. for ((i=1; i<=$#; i++)); do
  48. echo $i. ${!i}
  49. done
  50. echo
  51. REPLY=""
  52. while :
  53. do
  54. echo -n "SELECT 1-$#: "
  55. read
  56. if [[ $REPLY -ge 1 ]] && [[ $REPLY -le $# ]]; then
  57. return $REPLY
  58. fi
  59. done
  60. }
  61. SYS_TYPES=(Pi\ 3\ /\ Pi\ Zero\ W All\ other\ models)
  62. WATCHDOG_MODULES=(bcm2835_wdog bcm2708_wdog)
  63. OPTION_NAMES=(NO YES)
  64. echo -n "Enable boot-time read/write jumper? [y/N] "
  65. read
  66. if [[ "$REPLY" =~ (yes|y|Y)$ ]]; then
  67. INSTALL_RW_JUMPER=1
  68. echo -n "GPIO pin for R/W jumper: "
  69. read
  70. RW_PIN=$REPLY
  71. fi
  72. echo -n "Install GPIO-halt utility? [y/N] "
  73. read
  74. if [[ "$REPLY" =~ (yes|y|Y)$ ]]; then
  75. INSTALL_HALT=1
  76. echo -n "GPIO pin for halt button: "
  77. read
  78. HALT_PIN=$REPLY
  79. fi
  80. echo -n "Enable kernel panic watchdog? [y/N] "
  81. read
  82. if [[ "$REPLY" =~ (yes|y|Y)$ ]]; then
  83. INSTALL_WATCHDOG=1
  84. echo "Target system type:"
  85. selectN "${SYS_TYPES[0]}" \
  86. "${SYS_TYPES[1]}"
  87. WD_TARGET=$?
  88. fi
  89. # VERIFY SELECTIONS BEFORE CONTINUING --------------------------------------
  90. echo
  91. if [ $INSTALL_RW_JUMPER -eq 1 ]; then
  92. echo "Boot-time R/W jumper: YES (GPIO$RW_PIN)"
  93. else
  94. echo "Boot-time R/W jumper: NO"
  95. fi
  96. if [ $INSTALL_HALT -eq 1 ]; then
  97. echo "Install GPIO-halt: YES (GPIO$HALT_PIN)"
  98. else
  99. echo "Install GPIO-halt: NO"
  100. fi
  101. if [ $INSTALL_WATCHDOG -eq 1 ]; then
  102. echo "Enable watchdog: YES (${SYS_TYPES[WD_TARGET-1]})"
  103. else
  104. echo "Enable watchdog: NO"
  105. fi
  106. echo
  107. echo -n "CONTINUE? [y/N] "
  108. read
  109. if [[ ! "$REPLY" =~ ^(yes|y|Y)$ ]]; then
  110. echo "Canceled."
  111. exit 0
  112. fi
  113. # START INSTALL ------------------------------------------------------------
  114. # All selections have been validated at this point...
  115. # Given a filename, a regex pattern to match and a replacement string:
  116. # Replace string if found, else no change.
  117. # (# $1 = filename, $2 = pattern to match, $3 = replacement)
  118. replace() {
  119. grep $2 $1 >/dev/null
  120. if [ $? -eq 0 ]; then
  121. # Pattern found; replace in file
  122. sed -i "s/$2/$3/g" $1 >/dev/null
  123. fi
  124. }
  125. # Given a filename, a regex pattern to match and a replacement string:
  126. # If found, perform replacement, else append file w/replacement on new line.
  127. replaceAppend() {
  128. grep $2 $1 >/dev/null
  129. if [ $? -eq 0 ]; then
  130. # Pattern found; replace in file
  131. sed -i "s/$2/$3/g" $1 >/dev/null
  132. else
  133. # Not found; append on new line (silently)
  134. echo $3 | sudo tee -a $1 >/dev/null
  135. fi
  136. }
  137. # Given a filename, a regex pattern to match and a string:
  138. # If found, no change, else append file with string on new line.
  139. append1() {
  140. grep $2 $1 >/dev/null
  141. if [ $? -ne 0 ]; then
  142. # Not found; append on new line (silently)
  143. echo $3 | sudo tee -a $1 >/dev/null
  144. fi
  145. }
  146. # Given a filename, a regex pattern to match and a string:
  147. # If found, no change, else append space + string to last line --
  148. # this is used for the single-line /boot/cmdline.txt file.
  149. append2() {
  150. grep $2 $1 >/dev/null
  151. if [ $? -ne 0 ]; then
  152. # Not found; insert in file before EOF
  153. sed -i "s/\'/ $3/g" $1 >/dev/null
  154. fi
  155. }
  156. echo
  157. echo "Starting installation..."
  158. echo "Updating package index files..."
  159. apt-get update
  160. echo "Removing unwanted packages..."
  161. #apt-get remove -y --force-yes --purge triggerhappy logrotate dbus \
  162. # dphys-swapfile xserver-common lightdm fake-hwclock
  163. # Let's keep dbus...that includes avahi-daemon, a la 'raspberrypi.local',
  164. # also keeping xserver & lightdm for GUI login (WIP, not working yet)
  165. apt-get remove -y --force-yes --purge triggerhappy logrotate \
  166. dphys-swapfile fake-hwclock
  167. apt-get -y --force-yes autoremove --purge
  168. # Replace log management with busybox (use logread if needed)
  169. echo "Installing ntp and busybox-syslogd..."
  170. apt-get -y --force-yes install ntp busybox-syslogd; dpkg --purge rsyslog
  171. echo "Configuring system..."
  172. # Install boot-time R/W jumper test if requested
  173. GPIOTEST="gpio -g mode $RW_PIN up\n\
  174. if [ \`gpio -g read $RW_PIN\` -eq 0 ] ; then\n\
  175. \tmount -o remount,rw \/\n\
  176. \tmount -o remount,rw \/boot\n\
  177. fi\n"
  178. if [ $INSTALL_RW_JUMPER -ne 0 ]; then
  179. apt-get install -y --force-yes wiringpi
  180. # Check if already present in rc.local:
  181. grep "gpio -g read" /etc/rc.local >/dev/null
  182. if [ $? -eq 0 ]; then
  183. # Already there, but make sure pin is correct:
  184. sed -i "s/^.*gpio\ -g\ read.*$/$GPIOTEST/g" /etc/rc.local >/dev/null
  185. else
  186. # Not there, insert before final 'exit 0'
  187. sed -i "s/^exit 0/$GPIOTEST\\nexit 0/g" /etc/rc.local >/dev/null
  188. fi
  189. fi
  190. # Install watchdog if requested
  191. if [ $INSTALL_WATCHDOG -ne 0 ]; then
  192. apt-get install -y --force-yes watchdog
  193. # $MODULE is specific watchdog module name
  194. MODULE=${WATCHDOG_MODULES[($WD_TARGET-1)]}
  195. # Add to /etc/modules, update watchdog config file
  196. append1 /etc/modules $MODULE $MODULE
  197. replace /etc/watchdog.conf "#watchdog-device" "watchdog-device"
  198. replace /etc/watchdog.conf "#max-load-1" "max-load-1"
  199. # Start watchdog at system start and start right away
  200. # Raspbian Stretch needs this package installed first
  201. apt-get install -y --force-yes insserv
  202. insserv watchdog; /etc/init.d/watchdog start
  203. # Additional settings needed on Jessie
  204. append1 /lib/systemd/system/watchdog.service "WantedBy" "WantedBy=multi-user.target"
  205. systemctl enable watchdog
  206. # Set up automatic reboot in sysctl.conf
  207. replaceAppend /etc/sysctl.conf "^.*kernel.panic.*$" "kernel.panic = 10"
  208. fi
  209. # Install gpio-halt if requested
  210. if [ $INSTALL_HALT -ne 0 ]; then
  211. apt-get install -y --force-yes wiringpi
  212. echo "Installing gpio-halt in /usr/local/bin..."
  213. cd /tmp
  214. curl -LO https://github.com/adafruit/Adafruit-GPIO-Halt/archive/master.zip
  215. unzip master.zip
  216. cd Adafruit-GPIO-Halt-master
  217. make
  218. mv gpio-halt /usr/local/bin
  219. cd ..
  220. rm -rf Adafruit-GPIO-Halt-master
  221. # Add gpio-halt to /rc.local:
  222. grep gpio-halt /etc/rc.local >/dev/null
  223. if [ $? -eq 0 ]; then
  224. # gpio-halt already in rc.local, but make sure correct:
  225. sed -i "s/^.*gpio-halt.*$/\/usr\/local\/bin\/gpio-halt $HALT_PIN \&/g" /etc/rc.local >/dev/null
  226. else
  227. # Insert gpio-halt into rc.local before final 'exit 0'
  228. sed -i "s/^exit 0/\/usr\/local\/bin\/gpio-halt $HALT_PIN \&\\nexit 0/g" /etc/rc.local >/dev/null
  229. fi
  230. fi
  231. # Add fastboot, noswap and/or ro to end of /boot/cmdline.txt
  232. append2 /boot/cmdline.txt fastboot fastboot
  233. append2 /boot/cmdline.txt noswap noswap
  234. append2 /boot/cmdline.txt ro^o^t ro
  235. # Move /var/spool to /tmp
  236. rm -rf /var/spool
  237. ln -s /tmp /var/spool
  238. # Move /var/lib/lightdm and /var/cache/lightdm to /tmp
  239. rm -rf /var/lib/lightdm
  240. rm -rf /var/cache/lightdm
  241. ln -s /tmp /var/lib/lightdm
  242. ln -s /tmp /var/cache/lightdm
  243. # Make SSH work
  244. replaceAppend /etc/ssh/sshd_config "^.*UsePrivilegeSeparation.*$" "UsePrivilegeSeparation no"
  245. # bbro method (not working in Jessie?):
  246. #rmdir /var/run/sshd
  247. #ln -s /tmp /var/run/sshd
  248. # Change spool permissions in var.conf (rondie/Margaret fix)
  249. replace /usr/lib/tmpfiles.d/var.conf "spool\s*0755" "spool 1777"
  250. # Move dhcpd.resolv.conf to tmpfs
  251. touch /tmp/dhcpcd.resolv.conf
  252. rm /etc/resolv.conf
  253. ln -s /tmp/dhcpcd.resolv.conf /etc/resolv.conf
  254. # Make edits to fstab
  255. # make / ro
  256. # tmpfs /var/log tmpfs nodev,nosuid 0 0
  257. # tmpfs /var/tmp tmpfs nodev,nosuid 0 0
  258. # tmpfs /tmp tmpfs nodev,nosuid 0 0
  259. replace /etc/fstab "vfat\s*defaults\s" "vfat defaults,ro "
  260. replace /etc/fstab "ext4\s*defaults,noatime\s" "ext4 defaults,noatime,ro "
  261. append1 /etc/fstab "/var/log" "tmpfs /var/log tmpfs nodev,nosuid 0 0"
  262. append1 /etc/fstab "/var/tmp" "tmpfs /var/tmp tmpfs nodev,nosuid 0 0"
  263. append1 /etc/fstab "\s/tmp" "tmpfs /tmp tmpfs nodev,nosuid 0 0"
  264. # PROMPT FOR REBOOT --------------------------------------------------------
  265. echo "Done."
  266. echo
  267. echo "Settings take effect on next boot."
  268. echo
  269. echo -n "REBOOT NOW? [y/N] "
  270. read
  271. if [[ ! "$REPLY" =~ ^(yes|y|Y)$ ]]; then
  272. echo "Exiting without reboot."
  273. exit 0
  274. fi
  275. echo "Reboot started..."
  276. reboot
  277. exit 0