Skip to content

Commit 6feb0f3

Browse files
committed
2025-09-16 installer and menu improvements - master branch
Changes to `install.sh`: 1. Bumps version number to 2025v01. 2. Rather than defaulting to `~/IOTstack` or requiring the `IOTSTACK` environment variable to point to the correct directory, now gives precedence to the directory where `install.sh` is running. Maintains backwards compatibility with earlier methods. 3. Adds `dialout` to groups list. This is needed for `deconz` and should probably always have been present. Equivalent PiBuilder change made. 4. Adds `version_json()` function which returns JSON structure with: - version number (as above) - commit ID of `install.sh` - exit code of installer script 5. Adds `should_run_installer()` which can be invoked via: ``` $ ./install.sh should_run_installer ``` returning either "false" or "true". In essence, if `install.sh` is updated, its commit-ID will change and that will cause `should_run_installer` to return "true". The condition will persist until the installer is run to completion successfully and updates `~/IOTstack/.new_install` with the matching commit-ID. 6. Also supports: ``` $ ./install.sh version ``` which displays a JSON string containing the version (eg `2025v01'`), commit-ID of the current file, plus a return code of zero. 7. `handle_exit()` now writes above JSON structure to `.new_install` rather than either the exit code (current) or touching the file (older). The menu only senses the presence/absence of `.new_install` so it doesn't care about the contents. 8. Trixie has removed `/etc/timezone` so it is no longer possible to initialise `TZ` in `~/IOTstack.env` from that source. Now invokes: ``` timedatectl show --value --property=Timezone ``` This is backwards-compatible (tested on Bullseye and Bookworm). 9. Adds `pwgen` to dependencies (needed for a separate project I'm working on). Changes to `menu.sh` (new menu): 1. Removes duplicate-named but different implementations of `user_in_group()` functions. Functionality replaced with `do_required_groups_checks()` which checks for `dialout` membership as well as `docker` and `bluetooth` (as now). 2. `do_required_groups_checks()` called wherever older code did explicit checks for `docker` and `bluetooth`. 3. Adds do_installer_checks() which queries `install.sh` to see if it should be re-run. If yes, presents a dialog asking for permission to proceed. Signed-off-by: Phill Kelley <[email protected]>
1 parent 863b613 commit 6feb0f3

File tree

2 files changed

+185
-96
lines changed

2 files changed

+185
-96
lines changed

install.sh

Lines changed: 133 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
11
#!/usr/bin/env bash
22

33
# version - MUST be exactly 7 characters!
4-
UPDATE="2024v03"
5-
6-
echo " "
7-
echo " _____ ____ _______ _ installer _ "
8-
echo " |_ _/ __ \\__ __|| | $UPDATE | | "
9-
echo " | || | | | | |___| |_ __ _ ___| | __"
10-
echo " | || | | | | / __| __/ _\` |/ __| |/ /"
11-
echo " _| || |__| | | \\__ \\ || (_| | (__| < "
12-
echo " |_____\\____/ |_|___/\\__\\__,_|\\___|_|\\_\\"
13-
echo " "
14-
echo " "
4+
UPDATE="2025v01"
155

166
#----------------------------------------------------------------------
177
# The intention of this script is that it should be able to be run
@@ -24,11 +14,15 @@ echo " "
2414
# overuse of sudo is a very common problem among new IOTstack users
2515
[ "$EUID" -eq 0 ] && echo "This script should NOT be run using sudo" && exit 1
2616

27-
# this script should be run without arguments
28-
[ $# -ne 0 ] && echo "command line argument(s) $@ ignored"
17+
# where is this script is running?
18+
WHERE=$(dirname "$(realpath "$0")")
2919

30-
# assumption(s) which can be overridden
31-
IOTSTACK=${IOTSTACK:-"$HOME/IOTstack"}
20+
# if this script looks like it is running in a clone (or unzip) of
21+
# IOTstack then base operations there, otherwise default to the
22+
# standard location of ~/IOTstack. Either way, permit override by
23+
# prepending IOTSTACK=path to the script call.
24+
[ -d "$WHERE/.templates" -a -d "$WHERE/docs" ] && PROJECT="$WHERE" || PROJECT="$HOME/IOTstack"
25+
IOTSTACK=${IOTSTACK:-"$PROJECT"}
3226

3327
# derived path(s) - note that the menu knows about most of these so
3428
# they can't just be changed without a lot of care.
@@ -53,20 +47,139 @@ COMPOSE_SYMLINK_PATH="/usr/local/bin/docker-compose"
5347
CMDLINE_OPTIONS="cgroup_memory=1 cgroup_enable=memory"
5448

5549
# dependencies installed via apt
56-
APT_DEPENDENCIES="curl git jq python3-pip python3-dev python3-virtualenv uuid-runtime whiptail"
50+
APT_DEPENDENCIES="curl git jq pwgen python3-pip python3-dev python3-virtualenv uuid-runtime whiptail"
5751

5852
# minimum version requirements
5953
DOCKER_VERSION_MINIMUM="24"
6054
COMPOSE_VERSION_MINIMUM="2.20"
6155
PYTHON_VERSION_MINIMUM="3.9"
6256

6357
# best-practice for group membership
64-
DESIRED_GROUPS="docker bluetooth"
58+
DESIRED_GROUPS="docker bluetooth dialout"
6559

6660
# what to do at script completion (reboot takes precedence)
6761
REBOOT_REQUIRED=false
6862
LOGOUT_REQUIRED=false
6963

64+
#----------------------------------------------------------------------
65+
# versioning functions
66+
#----------------------------------------------------------------------
67+
68+
# version_json()
69+
#
70+
# Arguments
71+
# $1 exit code
72+
# Returns:
73+
# JSON string with version, commit ID and exit code
74+
# Note:
75+
# If $0 is not under Git control (eg a zip download of IOTstack) then
76+
# the commit field will be an empty string.
77+
#
78+
version_json() {
79+
echo "{\"version\": \"${UPDATE}\", \"commit\": \"$(git log -n 1 --pretty=format:%H -- "${0}" 2>/dev/null)\", \"exitCode\": ${1}}"
80+
}
81+
82+
83+
# should_run_installer()
84+
#
85+
# Arguments
86+
# none
87+
# Returns
88+
# "false" or "true"
89+
# Theory:
90+
# Under normal conditions, running install.sh will result in the
91+
# hint file (.new_install) having the JSON string returned by
92+
# version_json(), which contains the current version number and
93+
# current commit ID of install.sh, plus an exit code of zero.
94+
#
95+
# If IOTstack is downloaded as a zip rather than a clone, the commit
96+
# ID will be null but, other than placing a heavier reliance on the
97+
# version number **changing**, that does not actually matter.
98+
#
99+
# Historically, .new_install has evolved through three generations:
100+
#
101+
# Gen 1: A touch file where its mere existence signalled that
102+
# install.sh had been run.
103+
# Gen 2: A file recording the exit code of the most-recent run. The
104+
# menu has never actually taken advantage of this.
105+
# Gen 3: The JSON string returned by version_json().
106+
#
107+
# This function will return "true" if any of the following is true:
108+
#
109+
# 1. install.sh has never been run to perform an installation (or
110+
# PiBuilder has not faked things on this script's behalf). In
111+
# other words, this is the situation where .new_install is absent.
112+
# 2. The last run of install.sh either produced a Gen 2 file
113+
# (irrespective of the exit code value) or a Gen 3 file with a
114+
# non-zero exit code.
115+
# 3. Either/both the embedded version number or commit ID have
116+
# changed as a result of an update.
117+
#
118+
should_run_installer() {
119+
# does the hint file exist and is it non-empty?
120+
if [ -s "${IOTSTACK_INSTALLER_HINT}" ] ; then
121+
# yes! compare content
122+
if [ "$(cat "${IOTSTACK_INSTALLER_HINT}")" = "$(version_json 0)" ] ; then
123+
echo "false"
124+
return
125+
fi
126+
fi
127+
echo "true"
128+
}
129+
130+
131+
#----------------------------------------------------------------------
132+
# arguments
133+
#----------------------------------------------------------------------
134+
135+
# any arguments passed on command-line?
136+
if [ $# -gt 0 ] ; then
137+
138+
# vector on command verb
139+
case "${1}" in
140+
141+
"version" )
142+
echo "$(version_json 0)"
143+
;;
144+
145+
"should_run_installer" )
146+
echo "$(should_run_installer)"
147+
;;
148+
149+
*)
150+
cat <<-HELP
151+
152+
Usage:
153+
${SCRIPT} (with no arguments runs the installer)
154+
${SCRIPT} version - returns JSON version string
155+
${SCRIPT} should_run_installer - returns "false" or "true"
156+
${SCRIPT} help - displays this menu
157+
158+
HELP
159+
;;
160+
161+
esac
162+
163+
# normal exit (ie does not fall through to the menu)
164+
exit 0
165+
166+
fi
167+
168+
169+
#----------------------------------------------------------------------
170+
# main installer code
171+
#----------------------------------------------------------------------
172+
173+
echo " "
174+
echo " _____ ____ _______ _ installer _ "
175+
echo " |_ _/ __ \\__ __|| | $UPDATE | | "
176+
echo " | || | | | | |___| |_ __ _ ___| | __"
177+
echo " | || | | | | / __| __/ _\` |/ __| |/ /"
178+
echo " _| || |__| | | \\__ \\ || (_| | (__| < "
179+
echo " |_____\\____/ |_|___/\\__\\__,_|\\___|_|\\_\\"
180+
echo " "
181+
echo " "
182+
70183
#----------------------------------------------------------------------
71184
# Check script dependencies
72185
#----------------------------------------------------------------------
@@ -107,7 +220,7 @@ fi
107220
function handle_exit() {
108221

109222
# record the exit condition (if possible)
110-
[ -d "$IOTSTACK" ] && echo "$1" >"$IOTSTACK_INSTALLER_HINT"
223+
[ -d "$IOTSTACK" ] && echo "$(version_json $1)" >"$IOTSTACK_INSTALLER_HINT"
111224

112225
# inform the user
113226
echo -n "install.sh completed"
@@ -351,8 +464,9 @@ sudo chown -R "$USER:$USER" "$IOTSTACK/backups" "$IOTSTACK/services"
351464
[ -d "$IOTSTACK/backups/influxdb" ] && sudo chown -R "root:root" "$IOTSTACK/backups/influxdb"
352465

353466
# initialise docker-compose global environment file with system timezone
467+
# see https://git.gsi.de/chef/cookbooks/sys/-/issues/54
354468
if [ ! -f "$IOTSTACK_ENV" ] || [ $(grep -c "^TZ=" "$IOTSTACK_ENV") -eq 0 ] ; then
355-
echo "TZ=$(cat /etc/timezone)" >>"$IOTSTACK_ENV"
469+
echo "TZ=$(timedatectl show --value --property=Timezone)" >>"$IOTSTACK_ENV"
356470
fi
357471

358472
#----------------------------------------------------------------------

menu.sh

Lines changed: 52 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,6 @@ function command_exists() {
2020
command -v "$@" > /dev/null 2>&1
2121
}
2222

23-
function user_in_group()
24-
{
25-
# see if the group exists
26-
grep -q "^$1:" /etc/group;
27-
28-
# sense that the group does not exist
29-
if [ $? -ne 0 ]; then return 0; fi
30-
31-
# group exists - now check that the user is a member
32-
groups | grep -q "\b$1\b"
33-
}
34-
3523
function minimum_version_check() {
3624
# Usage: minimum_version_check required_version current_major current_minor current_build
3725
# Example: minimum_version_check "1.2.3" 1 2 3
@@ -104,19 +92,6 @@ function minimum_version_check() {
10492
echo "$VERSION_GOOD"
10593
}
10694

107-
function user_in_group()
108-
{
109-
if grep -q $1 /etc/group ; then
110-
if id -nGz "$USER" | grep -qzxF "$1"; then
111-
echo "true"
112-
else
113-
echo "false"
114-
fi
115-
else
116-
echo "notgroup"
117-
fi
118-
}
119-
12095
function check_git_updates()
12196
{
12297
UPSTREAM=${1:-'@{u}'}
@@ -134,6 +109,7 @@ function check_git_updates()
134109
echo "Diverged"
135110
fi
136111
}
112+
137113
function install_python3_and_deps() {
138114
CURR_PYTHON_VER="${1:-Unknown}"
139115
CURR_VIRTUALENV="${2:-Unknown}"
@@ -190,20 +166,30 @@ function do_python3_checks() {
190166
fi
191167
}
192168

193-
function do_env_setup() {
194-
echo "Setting up environment:"
195-
if [[ ! "$(user_in_group bluetooth)" == "notgroup" ]] && [[ ! "$(user_in_group bluetooth)" == "true" ]]; then
196-
echo "User is NOT in 'bluetooth' group. Adding:" >&2
197-
echo "sudo usermod -G bluetooth -a $USER" >&2
198-
echo "You will need to restart your system before the changes take effect."
199-
sudo usermod -G "bluetooth" -a $USER
200-
fi
169+
function should_add_user_to_group()
170+
{
171+
# sense group does not exist
172+
grep -q "^$1:" /etc/group || return 1
173+
# sense group exists and user is already a member
174+
groups | grep -q "\b$1\b" && return 1
175+
# group exists, user should be added
176+
return 0
177+
}
201178

202-
if [ ! "$(user_in_group docker)" == "true" ]; then
203-
echo "User is NOT in 'docker' group. Adding:" >&2
204-
echo "sudo usermod -G docker -a $USER" >&2
205-
echo "You will need to restart your system before the changes take effect."
206-
sudo usermod -G "docker" -a $USER
179+
function do_required_groups_checks() {
180+
# best-practice for group membership
181+
local DESIRED_GROUPS="docker bluetooth dialout"
182+
local LOGOUT_REQUIRED=false
183+
local GROUP
184+
for GROUP in $DESIRED_GROUPS ; do
185+
if should_add_user_to_group $GROUP ; then
186+
echo "sudo /usr/sbin/usermod -G $GROUP -a $USER" >&2
187+
sudo /usr/sbin/usermod -G $GROUP -a $USER
188+
LOGOUT_REQUIRED=true
189+
fi
190+
done
191+
if [ "$LOGOUT_REQUIRED" = "true" ] ; then
192+
echo "You will need to logout and login again before the changes take effect."
207193
fi
208194
}
209195

@@ -256,19 +242,7 @@ function do_docker_checks() {
256242
if (whiptail --title "Docker and Docker-Compose" --yesno "Docker is not currently installed, and is required to run IOTstack. Would you like to install docker and docker-compose now?\nYou will not be prompted again." 20 78); then
257243
[ -f .docker_notinstalled ] && rm .docker_notinstalled
258244
echo "Setting up environment:"
259-
if [[ ! "$(user_in_group bluetooth)" == "notgroup" ]] && [[ ! "$(user_in_group bluetooth)" == "true" ]]; then
260-
echo "User is NOT in 'bluetooth' group. Adding:" >&2
261-
echo "sudo usermod -G bluetooth -a $USER" >&2
262-
echo "You will need to restart your system before the changes take effect."
263-
sudo usermod -G "bluetooth" -a $USER
264-
fi
265-
266-
if [ ! "$(user_in_group docker)" == "true" ]; then
267-
echo "User is NOT in 'docker' group. Adding:" >&2
268-
echo "sudo usermod -G docker -a $USER" >&2
269-
echo "You will need to restart your system before the changes take effect."
270-
sudo usermod -G "docker" -a $USER
271-
fi
245+
do_required_groups_checks
272246
install_docker
273247
else
274248
touch .docker_notinstalled
@@ -296,22 +270,34 @@ function do_project_checks() {
296270
fi
297271
}
298272

299-
function do_env_checks() {
300-
GROUPSGOOD=0
301273

302-
if [[ ! "$(user_in_group bluetooth)" == "notgroup" ]] && [[ ! "$(user_in_group bluetooth)" == "true" ]]; then
303-
GROUPSGOOD=1
304-
echo "User is NOT in 'bluetooth' group" >&2
305-
fi
274+
function do_installer_checks() {
306275

307-
if [[ ! "$(user_in_group docker)" == "true" ]]; then
308-
GROUPSGOOD=1
309-
echo "User is NOT in 'docker' group" >&2
310-
fi
276+
# expected location of installer script is
277+
local INSTALLER_SCRIPT="${PWD}/install.sh"
278+
279+
# sense not present
280+
[ -x "${INSTALLER_SCRIPT}" ] || return
281+
282+
# ask installer if it should be re-run
283+
if [ "$(${INSTALLER_SCRIPT} should_run_installer)" = "true" ] ; then
284+
285+
# yes! seek permission to do that from the user
286+
"whiptail" \
287+
"--title" "Installer Update" \
288+
"--yesno" "The IOTstack installer has been updated. Re-run it now?" \
289+
7 78 3>&1 1>&2 2>&3 ; RC=$?
290+
291+
# did the user agree?
292+
if [ ${RC} -eq 0 ] ; then
293+
294+
# yes! run the installer without arguments
295+
${INSTALLER_SCRIPT}
296+
297+
fi
311298

312-
if [ "$GROUPSGOOD" == 1 ]; then
313-
echo "!! You might experience issues with docker or bluetooth. To fix run: ./menu.sh --run-env-setup"
314299
fi
300+
315301
}
316302

317303
# ----------------------------------------------
@@ -322,10 +308,11 @@ if [[ "$*" == *"--no-check"* ]]; then
322308
echo "Skipping preflight checks."
323309
else
324310
do_project_checks
325-
do_env_checks
311+
do_required_groups_checks
326312
do_python3_checks
327313
echo "Please enter sudo pasword if prompted"
328314
do_docker_checks
315+
do_installer_checks
329316

330317
if [[ "$DOCKER_VERSION_GOOD" == "true" ]] && \
331318
[[ "$PYTHON_VERSION_GOOD" == "true" ]]; then
@@ -348,19 +335,7 @@ do
348335
;;
349336
--run-env-setup) # Sudo cannot be run from inside functions.
350337
echo "Setting up environment:"
351-
if [[ ! "$(user_in_group bluetooth)" == "notgroup" ]] && [[ ! "$(user_in_group bluetooth)" == "true" ]]; then
352-
echo "User is NOT in 'bluetooth' group. Adding:" >&2
353-
echo "sudo usermod -G bluetooth -a $USER" >&2
354-
echo "You will need to restart your system before the changes take effect."
355-
sudo usermod -G "bluetooth" -a $USER
356-
fi
357-
358-
if [ ! "$(user_in_group docker)" == "true" ]; then
359-
echo "User is NOT in 'docker' group. Adding:" >&2
360-
echo "sudo usermod -G docker -a $USER" >&2
361-
echo "You will need to restart your system before the changes take effect."
362-
sudo usermod -G "docker" -a $USER
363-
fi
338+
do_required_groups_checks
364339
;;
365340
--encoding) ENCODING_TYPE=$2
366341
;;

0 commit comments

Comments
 (0)