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