forked from gcgarner/IOTstack
-
Notifications
You must be signed in to change notification settings - Fork 308
/
install.sh
executable file
·424 lines (365 loc) · 15.1 KB
/
install.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
#!/usr/bin/env bash
# version - MUST be exactly 7 characters!
UPDATE="2024v02"
echo " "
echo " _____ ____ _______ _ installer _ "
echo " |_ _/ __ \\__ __|| | $UPDATE | | "
echo " | || | | | | |___| |_ __ _ ___| | __"
echo " | || | | | | / __| __/ _\` |/ __| |/ /"
echo " _| || |__| | | \\__ \\ || (_| | (__| < "
echo " |_____\\____/ |_|___/\\__\\__,_|\\___|_|\\_\\"
echo " "
echo " "
#----------------------------------------------------------------------
# The intention of this script is that it should be able to be run
# multiple times WITHOUT doing any harm. If you propose changes, please
# make sure you test the script in both a "green fields" system AND on
# a working system where docker, docker-compose and IOTstack are already
# installed.
#----------------------------------------------------------------------
# overuse of sudo is a very common problem among new IOTstack users
[ "$EUID" -eq 0 ] && echo "This script should NOT be run using sudo" && exit 1
# this script should be run without arguments
[ $# -ne 0 ] && echo "command line argument(s) $@ ignored"
# assumption(s) which can be overridden
IOTSTACK=${IOTSTACK:-"$HOME/IOTstack"}
# derived path(s) - note that the menu knows about most of these so
# they can't just be changed without a lot of care.
IOTSTACK_ENV="$IOTSTACK/.env"
IOTSTACK_MENU_REQUIREMENTS="$IOTSTACK/requirements-menu.txt"
IOTSTACK_MENU_VENV_DIR="$IOTSTACK/.virtualenv-menu"
IOTSTACK_INSTALLER_HINT="$IOTSTACK/.new_install"
# git cloning options which can be overridden
# (needs special handling for the null case)
if [[ ! -v GIT_CLONE_OPTIONS ]] ; then
GIT_CLONE_OPTIONS="--filter=tree:0"
fi
# the expected installation location of docker-compose-plugin is
COMPOSE_PLUGIN_PATH="/usr/libexec/docker/cli-plugins/docker-compose"
# the default location of a symlink in the PATH pointing to the above is
COMPOSE_SYMLINK_PATH="/usr/local/bin/docker-compose"
# add these to /boot/cmdline.txt (if it exists)
CMDLINE_OPTIONS="cgroup_memory=1 cgroup_enable=memory"
# dependencies installed via apt
APT_DEPENDENCIES="curl git jq python3-pip python3-dev python3-virtualenv uuid-runtime whiptail"
# minimum version requirements
DOCKER_VERSION_MINIMUM="24"
COMPOSE_VERSION_MINIMUM="2.20"
PYTHON_VERSION_MINIMUM="3.9"
# best-practice for group membership
DESIRED_GROUPS="docker bluetooth"
# what to do at script completion (reboot takes precedence)
REBOOT_REQUIRED=false
LOGOUT_REQUIRED=false
#----------------------------------------------------------------------
# Check script dependencies
#----------------------------------------------------------------------
echo -e -n "\nChecking operating-system environment - "
# This script assumes apt and dpkg are available. That's more-or-less
# the same as saying Debian oe Debian-derived. If apt and/or dpkg are
# missing then there's not much that can be done.
if [ -z $(which apt) -o -z $(which dpkg) ] ; then
echo "fail"
unset ID
[ -f "/etc/os-release" ] && eval $(grep "^ID=" /etc/os-release)
if [ "$ID" = "debian" ] ; then
echo "This system looks like it is based on Debian but seems to be missing"
echo "some key utilities (apt and/or dpkg). That suggests something is wrong."
echo "This script can't proceed until those issues are resolved."
else
echo "Some key utilities that are needed by this script seem to be missing"
echo "from this system. Both the Advanced Package Tool (apt) and the Debian"
echo "Package Manager (dpkg) are core components of Debian and Debian-derived"
echo "distributions like Raspberry Pi OS (aka Raspbian). It looks like you"
echo "might be trying to install IOTstack on a system which isn't based on"
echo "Debian. IOTstack has only ever been tested on Debian-based distributions"
echo "and is not qualified for other Linux or Unix distributions. This script"
echo "can't proceed."
fi
# direct exit - not via handle_exit()
exit 1
else
echo "pass"
fi
#----------------------------------------------------------------------
# script memory (exit conditions)
#----------------------------------------------------------------------
function handle_exit() {
# record the exit condition (if possible)
[ -d "$IOTSTACK" ] && echo "$1" >"$IOTSTACK_INSTALLER_HINT"
# inform the user
echo -n "install.sh completed"
# advise if should be re-run
[ $1 -ne 0 ] && echo -n " - but should be re-run"
# reboot takes precedence over logout
if [ "$REBOOT_REQUIRED" = "true" ] ; then
echo " - a reboot is required."
sleep 2
sudo reboot
elif [ "$LOGOUT_REQUIRED" = "true" ] ; then
echo " - a logout is required."
sleep 2
# iterate ancestor processes
for ANCESTOR in $(ps -o ppid=) ; do
# find first process belonging to current user
if [ "$(ps -p $ANCESTOR -o user=)" = "$USER" ] ; then
# kill it
kill -HUP $ANCESTOR
fi
done
# should not reach this
sleep 2
fi
# exit as instructed
echo ""
exit $1
}
#----------------------------------------------------------------------
# IOTstack dependencies installed via apt
#----------------------------------------------------------------------
echo -e "\nUpdating Advanced Package Tool (apt) caches"
sudo apt update
echo -e "\nInstalling/updating IOTstack dependencies"
sudo apt install -y $APT_DEPENDENCIES
#----------------------------------------------------------------------
# docker + compose installation
#----------------------------------------------------------------------
# is docker installed?
if [ -z $(which docker) ] ; then
# no! use the convenience script
echo -e "\nInstalling docker and docker-compose-plugin using the 'convenience script'"
echo "from https://get.docker.com ..."
curl -fsSL https://get.docker.com | sudo sh
if [ $? -eq 0 ] ; then
echo -e "\nInstallation of docker and docker-compose-plugin completed normally."
REBOOT_REQUIRED=true
else
echo -e "\nThe 'convenience script' returned an error. Unable to proceed."
handle_exit 1
fi
else
echo -e -n "\nDocker is already installed - checking your version - "
DOCKER_VERSION_INSTALLED="$(docker version -f "{{.Server.Version}}")"
if dpkg --compare-versions "$DOCKER_VERSION_MINIMUM" "gt" "$DOCKER_VERSION_INSTALLED" ; then
echo "fail"
echo "You have an obsolete version of Docker installed:"
echo " Minimum version required: $DOCKER_VERSION_MINIMUM"
echo " Version currently installed: $DOCKER_VERSION_INSTALLED"
echo "Try updating your system by running:"
echo " \$ sudo apt update && sudo apt upgrade -y"
echo " \$ docker version -f {{.Server.Version}}"
echo "If the version number changes, try re-running this script. If the"
echo "version number does not change, you may need to uninstall both"
echo "docker and docker-compose. If any containers are running, stop"
echo "them, then run:"
echo " \$ sudo systemctl stop docker.service"
echo " \$ sudo systemctl disable docker.service"
echo " \$ sudo apt -y purge docker-ce docker-ce-cli containerd.io docker-compose"
echo " \$ sudo apt -y autoremove"
echo " \$ sudo reboot"
echo "and then re-run this script after the reboot."
handle_exit 1
else
echo "pass"
fi
fi
#----------------------------------------------------------------------
# group memberships
#----------------------------------------------------------------------
function should_add_user_to_group()
{
# sense group does not exist
grep -q "^$1:" /etc/group || return 1
# sense group exists and user is already a member
groups | grep -q "\b$1\b" && return 1
# group exists, user should be added
return 0
}
# check group membership
echo -e -n "\nChecking group memberships"
for GROUP in $DESIRED_GROUPS ; do
echo -n " - $GROUP "
if should_add_user_to_group $GROUP ; then
echo -n "adding $USER"
sudo /usr/sbin/usermod -G $GROUP -a $USER
LOGOUT_REQUIRED=true
else
echo -n "pass"
fi
done
echo ""
#----------------------------------------------------------------------
# docker-compose setup/verification
#----------------------------------------------------------------------
# Correct installation of docker-compose is defined as the result of
# `which docker-compose` (typically $COMPOSE_SYMLINK_PATH) being a
# symlink pointing to the expected location of docker-compose-plugin as
# it is installed by the convenience script ($COMPOSE_PLUGIN_PATH).
# Alternatively, if `which docker-compose` returns null but the plugin
# is in the expected location, the necessary symlink can be created by
# this script and then docker-compose will be installed "correctly".
function is_python_script() {
[ $(file -b "$1" | grep -c "^Python script") -gt 0 ] && return 0
return 1
}
# presume docker-compose not installed correctly
COMPOSE_INSTALLED_CORRECTLY=false
# search for docker-compose in the PATH
COMPOSE_CMD_PATH=$(which docker-compose)
# is docker-compose in the PATH?
echo -e -n "\nChecking whether docker-compose is installed correctly - "
if [ -n "$COMPOSE_CMD_PATH" ] ; then
# yes! is it a symlink and does the symlink point to a file?
if [ -L "$COMPOSE_CMD_PATH" -a -f "$COMPOSE_CMD_PATH" ] ; then
# yes! fetch the inode of what the link points to
COMPOSE_CMD_INODE=$(stat -c "%i" -L "$COMPOSE_CMD_PATH")
# does the plugin exist at the expected path?
if [ -f "$COMPOSE_PLUGIN_PATH" ] ; then
# yes! fetch the plugin's inode
COMPOSE_PLUGIN_INODE=$(stat -c "%i" "$COMPOSE_PLUGIN_PATH")
# are the inodes the same?
if [ $COMPOSE_CMD_INODE -eq $COMPOSE_PLUGIN_INODE ] ; then
# yes! thus docker-compose is installed correctly
COMPOSE_INSTALLED_CORRECTLY=true
fi
fi
fi
else
# no! does the plugin exist at the expected location?
if [ -f "$COMPOSE_PLUGIN_PATH" ] ; then
# yes! so, no command, but plugin present. Fix with symlink
sudo ln -s "$COMPOSE_PLUGIN_PATH" "$COMPOSE_SYMLINK_PATH"
# and now compose is installed correctly
COMPOSE_INSTALLED_CORRECTLY=true
else
echo "fail"
echo "Your system has docker installed but doesn't seem to have either"
echo "docker-compose or docker-compose-plugin. Try running:"
echo " \$ sudo apt install -y docker-compose-plugin"
echo "and then try re-running this script."
handle_exit 1
fi
fi
# is docker-compose installed correctly?
if [ "$COMPOSE_INSTALLED_CORRECTLY" = "true" ] ; then
echo "pass"
echo -e -n "\nChecking your version of docker-compose - "
COMPOSE_VERSION_INSTALLED="$(docker-compose version --short)"
if dpkg --compare-versions "$COMPOSE_VERSION_MINIMUM" "gt" "$COMPOSE_VERSION_INSTALLED" ; then
echo "fail"
echo "You have an obsolete version of docker-compose installed:"
echo " Minimum version required: $COMPOSE_VERSION_MINIMUM"
echo " Version currently installed: $COMPOSE_VERSION_INSTALLED"
echo "Try updating your system by running:"
echo " \$ sudo apt update && sudo apt upgrade -y"
echo "and then try re-running this script."
handle_exit 1
else
echo "pass"
fi
else
echo "fail"
echo "docker-compose is not installed correctly. The most common reason is"
echo "having installed docker and docker-compose without using the official"
echo "'convenience script'. You may be able to solve this problem by running"
if is_python_script "$COMPOSE_CMD_PATH" ; then
echo " \$ export PIP_BREAK_SYSTEM_PACKAGES=1"
echo " \$ pip3 uninstall -y docker-compose"
echo " \$ sudo pip3 uninstall -y docker-compose"
echo " (ignore any errors from those commands)"
else
echo " \$ sudo apt purge -y docker-compose"
fi
echo "and then try re-running this script."
handle_exit 1
fi
#----------------------------------------------------------------------
# Clone IOTstack repo
#----------------------------------------------------------------------
# does the IOTstack folder already exist?
if [ ! -d "$IOTSTACK" ] ; then
# no! clone from GitHub
if [ -n "$GIT_CLONE_OPTIONS" ] ; then
echo -e "\nCloning the IOTstack repository from GitHub using options $GIT_CLONE_OPTIONS"
git clone "$GIT_CLONE_OPTIONS" https://github.com/SensorsIot/IOTstack.git "$IOTSTACK"
else
echo -e "\nCloning the full IOTstack repository from GitHub"
git clone https://github.com/SensorsIot/IOTstack.git "$IOTSTACK"
fi
if [ $? -eq 0 -a -d "$IOTSTACK" ] ; then
echo "IOTstack cloned successfully into $IOTSTACK"
mkdir -p "$IOTSTACK/backups" "$IOTSTACK/services"
else
echo "Unable to clone IOTstack (likely a git or network error)"
handle_exit 1
fi
else
echo -e "\n$IOTSTACK already exists - no need to clone from GitHub"
fi
# initialise docker-compose global environment file with system timezone
if [ ! -f "$IOTSTACK_ENV" ] || [ $(grep -c "^TZ=" "$IOTSTACK_ENV") -eq 0 ] ; then
echo "TZ=$(cat /etc/timezone)" >>"$IOTSTACK_ENV"
fi
#----------------------------------------------------------------------
# Python support
#----------------------------------------------------------------------
# make sure "python" invokes "python3"
PYTHON_INVOKES=$(update-alternatives --list python 2>/dev/null)
PYTHON3_PATH=$(which python3)
if [ "$PYTHON_INVOKES" != "$PYTHON3_PATH" ] ; then
echo -e "\nMaking python3 the default"
sudo update-alternatives --install /usr/bin/python python "$PYTHON3_PATH" 1
fi
echo -e -n "\nChecking your version of Python - "
PYTHON_VERSION_INSTALLED="$(python --version)"
PYTHON_VERSION_INSTALLED="${PYTHON_VERSION_INSTALLED#*Python }"
if dpkg --compare-versions "$PYTHON_VERSION_MINIMUM" "gt" "$PYTHON_VERSION_INSTALLED" ; then
echo "fail"
echo "You have an obsolete version of python installed:"
echo " Minimum version required: $PYTHON_VERSION_MINIMUM"
echo " Version currently installed: $PYTHON_VERSION_INSTALLED"
echo "Try updating your system by running:"
echo " \$ sudo apt update && sudo apt upgrade -y"
echo " \$ python --version"
echo "If the version number changes, try re-running this script. If not, you"
echo "may need to reinstall python3-pip, python3-dev and python3-virtualenv."
handle_exit 1
else
echo "pass"
fi
# implement menu requirements
if [ -e "$IOTSTACK_MENU_REQUIREMENTS" ] ; then
echo -e "\nChecking and updating IOTstack dependencies (pip)"
echo "Note: pip3 installs bypass externally-managed environment check"
PIP_BREAK_SYSTEM_PACKAGES=1 pip3 install -U -r "$IOTSTACK_MENU_REQUIREMENTS"
fi
# trigger re-creation of venv on next menu launch. Strictly speaking,
# sudo is not required for this but it protects against accidental prior
# use of sudo when the venv was created
sudo rm -rf "$IOTSTACK_MENU_VENV_DIR"
#----------------------------------------------------------------------
# Raspberry Pi boot options
#----------------------------------------------------------------------
# set cmdline options (if possible - Raspberry Pi dependency)
TARGET="/boot/firmware/cmdline.txt"
[ -e "$TARGET" ] || TARGET="/boot/cmdline.txt"
if [ -e "$TARGET" ] ; then
echo -e -n "\nChecking Raspberry Pi boot-time options - "
unset APPEND
for OPTION in $CMDLINE_OPTIONS ; do
if [ $(grep -c "$OPTION" "$TARGET") -eq 0 ] ; then
APPEND="$APPEND $OPTION"
fi
done
if [ -n "$APPEND" ] ; then
echo "appending$APPEND"
sudo sed -i.bak "s/$/$APPEND/" "$TARGET"
REBOOT_REQUIRED=true
else
echo "no modifications needed"
fi
fi
#----------------------------------------------------------------------
# normal exit
#----------------------------------------------------------------------
handle_exit 0