#!/bin/bash
# Copyright (C) 2018, 2019 Lee C. Bussy (@LBussy)
#
# This file is part of LBussy's BrewPi Tools Remix (BrewPi-Tools-RMX).
#
# BrewPi Tools RMX is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# BrewPi Tools RMX is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BrewPi Tools RMX. If not, see .
# These scripts were originally a part of brewpi-tools, an installer for
# the BrewPi project. Legacy support (for the very popular Arduino
# controller) seems to have been discontinued in favor of new hardware.
# All credit for the original brewpi-tools goes to @elcojacobs,
# @vanosg, @routhcr, @ajt2 and I'm sure many more contributors around
# the world. My apologies if I have missed anyone; those were the names
# listed as contributors on the Legacy branch.
# See: 'original-license.md' for notes about the original project's
# license and credits.
# Set branch
BRANCH=main
############
### Global Declarations
############
# General constants
declare THISSCRIPT GITBRNCH GITURL GITPROJ PACKAGE CHAMBER VERBOSE
declare REPLY SOURCE SCRIPTSOURCE SCRIPTPATH CHAMBERNAME CMDLINE GITRAW GITHUB
declare SCRIPTNAME GITCMD GITTEST APTPACKAGES VERBOSE LINK
# Color/character codes
declare BOLD SMSO RMSO FGBLK FGRED FGGRN FGYLW FGBLU FGMAG FGCYN FGWHT FGRST
declare BGBLK BGRED BGGRN BGYLW BGBLU BGMAG BGCYN BGWHT BGRST DOT HHR LHR RESET
# Set branch
if [ -z "$BRANCH" ]; then GITBRNCH="master"; else GITBRNCH="$BRANCH"; fi
THISSCRIPT="bootstrap.sh"
LINK="https://raw.githubusercontent.com/brewpi-remix/brewpi-tools-rmx/$GITBRNCH/bootstrap.sh"
############
### Init
############
init() {
# Set up some project variables we won't have running as a curled script
PACKAGE="BrewPi-Tools-RMX"
CMDLINE="curl -L $LINK | BRANCH=$GITBRNCH sudo bash"
# These should stay the same
GITRAW="https://raw.githubusercontent.com/brewpi-remix"
GITHUB="https://github.com/brewpi-remix"
# Cobble together some strings
SCRIPTNAME="${THISSCRIPT%%.*}"
GITPROJ="${PACKAGE,,}"
GITHUB="$GITHUB/$GITPROJ.git"
GITRAW="$GITRAW/$GITPROJ/$GITBRNCH/$THISSCRIPT"
GITCMD="$GITHUB"
# Website for network test
GITTEST="$GITHUB"
# Packages to be installed/checked via apt
APTPACKAGES="git"
}
############
### Handle logging
############
timestamp() {
# Add date in '2019-02-26 08:19:22' format to log
[[ "$VERBOSE" == "true" ]] && length=999 || length=60 # Allow full logging
while read -r; do
# Clean and trim line to 60 characters to allow for timestamp on one line
REPLY="$(clean "$REPLY" $length)"
# Strip blank lines
if [ -n "$REPLY" ]; then
# Add date in '2019-02-26 08:19:22' format to log
printf '%(%Y-%m-%d %H:%M:%S)T %s\n' -1 "$REPLY"
fi
done
}
clean() {
# Cleanup log line
local input length dot
input="$1"
length="$2"
# Even though this is defined in term() we need it earlier
dot="$(tput sc)$(tput setaf 0)$(tput setab 0).$(tput sgr 0)$(tput rc)"
# If we lead the line with our semaphore, return a blank line
if [[ "$input" == "$dot"* ]]; then echo ""; return; fi
# Strip color codes
input="$(echo "$input" | sed 's,\x1B[[(][0-9;]*[a-zA-Z],,g')"
# Strip beginning spaces
input="$(printf "%s" "${input#"${input%%[![:space:]]*}"}")"
# Strip ending spaces
input="$(printf "%s" "${input%"${input##*[![:space:]]}"}")"
# Squash any repeated whitespace within string
input="$(echo "$input" | awk '{$1=$1};1')"
# Log only first $length chars to allow for date/time stamp
input="$(echo "$input" | cut -c-"$length")"
echo "$input"
}
log() {
local thisscript scriptname shadow homepath
[[ "$*" == *"-nolog"* ]] && return # Turn off logging
# Set up our local variables
local thisscript scriptname realuser homepath shadow
# Explicit scriptname (creates log name) since we start
# before the main script
thisscript="bootstrap.sh"
scriptname="${thisscript%%.*}"
# Get home directory for logging
if [ -n "$SUDO_USER" ]; then realuser="$SUDO_USER"; else realuser=$(whoami); fi
shadow="$( (getent passwd "$realuser") 2>&1)"
if [ -n "$shadow" ]; then
homepath=$(echo "$shadow" | cut -d':' -f6)
else
echo -e "\nERROR: Unable to retrieve $realuser's home directory. Manual install"
echo -e "may be necessary."
exit 1
fi
# Tee all output to log file in home directory
sudo -u "$realuser" touch "$homepath/$scriptname.log"
exec > >(tee >(timestamp >> "$homepath/$scriptname.log")) 2>&1
}
############
### Command line arguments
############
# usage outputs to stdout the --help usage message.
usage() {
cat << EOF
$PACKAGE $THISSCRIPT
Usage: sudo ./$THISSCRIPT"
EOF
}
# version outputs to stdout the --version message.
version() {
cat << EOF
$THISSCRIPT ($PACKAGE)
Copyright (C) 2018, 2019 Lee C. Bussy (@LBussy)
This is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
There is NO WARRANTY, to the extent permitted by law.
EOF
}
# Parse arguments and call usage or version
arguments() {
local arg
while [[ "$#" -gt 0 ]]; do
arg="$1"
case "$arg" in
--h* )
usage; exit 0 ;;
--v* )
version; exit 0 ;;
* )
break;;
esac
done
}
############
### Make sure command is running with sudo
############
checkroot() {
local retval shadow
if [ -n "$SUDO_USER" ]; then REALUSER="$SUDO_USER"; else REALUSER=$(whoami); fi
if [ "$REALUSER" == "root" ]; then
# We're not gonna run as the root user
echo -e "\nThis script may not be run from the root account, use 'sudo' instead."
exit 1
fi
### Check if we have root privs to run
if [[ "$EUID" -ne 0 ]]; then
sudo -n true 2> /dev/null
retval="$?"
if [ "$retval" -eq 0 ]; then
echo -e "\nNot running as root, relaunching correctly."
sleep 2
eval "$CMDLINE"
exit "$?"
else
# sudo not available, give instructions
echo -e "\nThis script must be run with root privileges."
echo -e "Enter the following command as one line:"
echo -e "$CMDLINE" 1>&2
exit 1
fi
fi
# And get the user home directory
shadow="$( (getent passwd "$REALUSER") 2>&1)"
retval="$?"
if [ "$retval" -eq 0 ]; then
HOMEPATH="$(echo "$shadow" | cut -d':' -f6)"
else
echo -e "\nUnable to retrieve $REALUSER's home directory. Manual install may be necessary."
exit 1
fi
}
############
### Provide terminal escape codes
############
term() {
local retval
# If we are colors capable, allow them
tput colors > /dev/null 2>&1
retval="$?"
if [ "$retval" == "0" ]; then
BOLD=$(tput bold) # Start bold text
SMSO=$(tput smso) # Start "standout" mode
RMSO=$(tput rmso) # End "standout" mode
FGBLK=$(tput setaf 0) # FG Black
FGRED=$(tput setaf 1) # FG Red
FGGRN=$(tput setaf 2) # FG Green
FGYLW=$(tput setaf 3) # FG Yellow
FGBLU=$(tput setaf 4) # FG Blue
FGMAG=$(tput setaf 5) # FG Magenta
FGCYN=$(tput setaf 6) # FG Cyan
FGWHT=$(tput setaf 7) # FG White
FGRST=$(tput setaf 9) # FG Reset to default color
BGBLK=$(tput setab 0) # BG Black
BGRED=$(tput setab 1) # BG Red
BGGRN=$(tput setab 2) # BG Green$(tput setaf $fg_color)
BGYLW=$(tput setab 3) # BG Yellow
BGBLU=$(tput setab 4) # BG Blue
BGMAG=$(tput setab 5) # BG Magenta
BGCYN=$(tput setab 6) # BG Cyan
BGWHT=$(tput setab 7) # BG White
BGRST=$(tput setab 9) # BG Reset to default color
# Some constructs
# "Invisible" period (black FG/BG and a backspace)
DOT="$(tput sc)$(tput setaf 0)$(tput setab 0).$(tput sgr 0)$(tput rc)"
HHR="$(eval printf %.0s═ '{1..'"${COLUMNS:-$(tput cols)}"\}; echo)"
LHR="$(eval printf %.0s─ '{1..'"${COLUMNS:-$(tput cols)}"\}; echo)"
RESET=$(tput sgr0) # FG/BG reset to default color
fi
}
############
### Functions to catch/display errors during execution
############
warn() {
local fmt
fmt="$1"
command shift 2>/dev/null
echo -e "$fmt"
echo -e "${@}"
echo -e "\n*** ERROR ERROR ERROR ERROR ERROR ***" > /dev/tty
echo -e "-------------------------------------" > /dev/tty
echo -e "\nSee above lines for error message." > /dev/tty
echo -e "Setup NOT completed.\n" > /dev/tty
}
die() {
local st
st="$?"
warn "$@"
exit "$st"
}
############
### Instructions
############
instructions() {
local any sp14 sp17 sp22
sp14="$(printf ' %.0s' {1..14})" sp17="$(printf ' %.0s' {1..17})"
sp22="$(printf ' %.0s' {1..22})"
clear
# Note: $(printf ...) hack adds spaces at beg/end to support non-black BG
cat << EOF
$DOT$BGBLK$FGYLW$sp14 ___ ___ _ ___ _ $sp22
$DOT$BGBLK$FGYLW$sp14| _ )_ _ _____ __ _| _ (_) | _ \___ _ __ (_)_ __ $sp17
$DOT$BGBLK$FGYLW$sp14| _ \ '_/ -_) V V / _/ | | / -_) ' \| \ \ / $sp17
$DOT$BGBLK$FGYLW$sp14|___/_| \___|\_/\_/|_| |_| |_|_\___|_|_|_|_/_\_\ $sp17
$DOT$BGBLK$FGGRN$HHR$RESET
You will be presented with some choices during the install. Most frequently
you will see a 'yes or no' choice, with the default choice capitalized like
so: [y/N]. Default means if you hit without typing anything, you will
make the capitalized choice, i.e. hitting when you see [Y/n] will
default to 'yes.'
Yes/no choices are not case sensitive. However; passwords, system names and
install paths are. Be aware of this. There is generally no difference between
'y', 'yes', 'YES', 'Yes'; you get the idea. In some areas you are asked for a
path; the default/recommended choice is in braces like: [/home/brewpi].
Pressing without typing anything will take the default/recommended
choice.
EOF
read -n 1 -s -r -p "Press any key when you are ready to proceed. " < /dev/tty
echo -e ""
}
############
### Check for default 'pi' password and gently prompt to change it now
############
checkpass() {
local user_exists salt extpass match badpwd yn setpass
user_exists=$(id -u 'pi' > /dev/null 2>&1; echo $?)
if [ "$user_exists" -eq 0 ]; then
salt=$(getent shadow "pi" | cut -d$ -f3)
extpass=$(getent shadow "pi" | cut -d: -f2)
match=$(python3 -c 'import crypt; print(crypt.crypt("'"raspberry"'", "$6$'${salt}'"))')
[ "${match}" == "${extpass}" ] && badpwd=true || badpwd=false
if [ "$badpwd" = true ]; then
echo -e "\nDefault password found for the 'pi' account. This should be changed."
while true; do
read -rp "Do you want to change the password now? [Y/n]: " yn < /dev/tty
case "$yn" in
'' ) setpass=1; break ;;
[Yy]* ) setpass=1; break ;;
[Nn]* ) break ;;
* ) echo "Enter [y]es or [n]o." ;;
esac
done
fi
if [ -n "$setpass" ]; then
echo
until passwd pi < /dev/tty; do sleep 2; echo; done
echo -e "\nYour password has been changed, remember it or write it down now."
sleep 5
fi
fi
}
############
### Set timezone
###########
settime() {
local date tz
date=$(date)
while true; do
echo -e "\nThe time is currently set to $date."
tz="$(date +%Z)"
if [ "$tz" == "GMT" ] || [ "$tz" == "BST" ]; then
# Probably never been set
read -rp "Is this correct? [y/N]: " yn < /dev/tty
case "$yn" in
[Yy]* ) echo ; break ;;
[Nn]* ) dpkg-reconfigure tzdata; break ;;
* ) dpkg-reconfigure tzdata; break ;;
esac
else
# Probably been set
read -rp "Is this correct? [Y/n]: " yn < /dev/tty
case "$yn" in
[Nn]* ) dpkg-reconfigure tzdata; break ;;
[Yy]* ) break ;;
* ) break ;;
esac
fi
done
}
############
### Change hostname
###########
host_name() {
local oldHostName yn sethost host1 host2 newHostName
oldHostName=$(hostname)
if [ "$oldHostName" = "raspberrypi" ]; then
while true; do
echo -e "\nYour hostname is set to '$oldHostName'. Each machine on your network should"
echo -e "have a unique name to prevent issues. Do you want to change it now, maybe"
read -rp "to 'brewpi'? [Y/n]: " yn < /dev/tty
case "$yn" in
'' ) sethost=1; break ;;
[Yy]* ) sethost=1; break ;;
[Nn]* ) break ;;
* ) echo "Enter [y]es or [n]o." ; sleep 1 ; echo ;;
esac
done
echo
if [ -n "$sethost" ]; then
echo -e "You will now be asked to enter a new hostname."
while
read -rp "Enter new hostname: " host1 < /dev/tty
read -rp "Enter new hostname again: " host2 < /dev/tty
[[ -z "$host1" || "$host1" != "$host2" ]]
do
echo -e "\nHost names blank or do not match.\n";
sleep 1
done
echo
newHostName=$(echo "$host1" | awk '{print tolower($0)}')
eval "sed -i 's/$oldHostName/$newHostName/g' /etc/hosts"||die
eval "sed -i 's/$oldHostName/$newHostName/g' /etc/hostname"||die
hostnamectl set-hostname "$newHostName"
/etc/init.d/avahi-daemon restart
echo -e "\nYour hostname has been changed to '$newHostName'."
echo -e "\n(If your hostname is part of your prompt, your prompt will not change until"
echo -e "you log out and in again. This will have no effect on anything but the way"
echo -e "the prompt looks.)"
sleep 5
fi
fi
}
############
### Install or update required packages
############
packages() {
local lastUpdate nowTime pkgOk upgradesAvail pkg
echo -e "\nUpdating any expired apt keys."
for K in $(apt-key list 2> /dev/null | grep expired | cut -d'/' -f2 | cut -d' ' -f1); do
sudo apt-key adv --recv-keys --keyserver keys.gnupg.net $K;
done
echo -e "\nFixing any broken installations."
sudo apt-get --fix-broken install -y||die
# Run 'apt update' if last run was > 1 week ago
lastUpdate=$(stat -c %Y /var/lib/apt/lists)
nowTime=$(date +%s)
if [ $((nowTime - lastUpdate)) -gt 604800 ]; then
echo -e "\nLast apt update was over a week ago. Running apt update before updating"
echo -e "dependencies."
apt-get update -yq||die
fi
# Now install any necessary packages if they are not installed
echo -e "\nChecking and installing required dependencies via apt."
for pkg in $APTPACKAGES; do
pkgOk=$(dpkg-query -W --showformat='${Status}\n' "$pkg" | \
grep "install ok installed")
if [ -z "$pkgOk" ]; then
echo -e "\nInstalling '$pkg'."
apt-get install "$pkg" -y -q=2||die
fi
done
# Get list of installed packages with updates available
upgradesAvail=$(dpkg --get-selections | xargs apt-cache policy {} | \
grep -1 Installed | sed -r 's/(:|Installed: |Candidate: )//' | \
uniq -u | tac | sed '/--/I,+1 d' | tac | sed '$d' | sed -n 1~2p)
# Loop through the required packages and see if they need an upgrade
for pkg in $APTPACKAGES; do
if [[ "$upgradesAvail" == *"$pkg"* ]]; then
echo -e "\nUpgrading '$pkg'."
apt-get install "$pkg" -y -q=2||die
fi
done
}
############
### Check for an existing BrewPi installation
############
check_brewpi() {
if [ -d "$HOMEPATH/$GITPROJ" ]; then
if [ -n "$(ls -A "$HOMEPATH/$GITPROJ")" ]; then
echo -e "\nWarning: $HOMEPATH/$GITPROJ exists and is not empty."
else
echo -e "\nWarning: $HOMEPATH/$GITPROJ exists."
fi
echo -e "\nIf you are sure you do not need it, or you are starting over completely, we can"
echo -e "delete the old repo by accepting the below prompt. If you are running multi-"
echo -e "chamber and are trying to add a new chamber, select 'N' below, and add a new"
echo -e "chamber by executing: 'sudo $HOMEPATH/$GITPROJ/install.sh'\n"
read -rp "Remove $HOMEPATH/$GITPROJ? [y/N] " < /dev/tty
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
rm -fr "${HOMEPATH:?}/$GITPROJ"
else
echo -e "\nLeaving $HOMEPATH/$GITPROJ in place and exiting."
exit 1
fi
fi
}
############
### Clone BrewPi-Tools-RMX repo
############
clonetools() {
echo -e "\nCloning $GITPROJ repo."
eval "sudo -u $REALUSER git clone $GITCMD $HOMEPATH/$GITPROJ"||die
cd "$HOMEPATH/$GITPROJ"
eval "sudo -u $REALUSER git checkout $GITBRNCH"||die
cd "$HOMEPATH"
}
############
### Main function
############
main() {
#[[ "$*" == *"-verbose"* ]] && VERBOSE=true # Do not trim logs
VERBOSE=true # Do not trim logs
log "$@" # Start logging
init "$@" # Get constants
arguments "$@" # Check command line arguments
echo -e "\n***Script $THISSCRIPT starting.***\n"
sysver="$(cat "/etc/os-release" | grep 'PRETTY_NAME' | cut -d '=' -f2)"
sysver="$(sed -e 's/^"//' -e 's/"$//' <<<"$sysver")"
echo -e "\nRunning on: $sysver\n"
checkroot # Make sure we are su into root
term # Add term command constants
instructions # Show instructions
check_brewpi # See if BrewPi is installed
checkpass # Check for default password
settime # Set timezone
host_name # Change hostname
packages # Install and update required packages
clonetools # Clone tools repo
eval "$HOMEPATH/$GITPROJ/install.sh -nolog" || die # Start installer
}
############
### Start the script
############
main "$@" && exit 0